From 396e5c2ca3ec8cffcfb1f5730a9887a033466ba9 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Mon, 15 Jul 2024 14:02:11 +0700 Subject: [PATCH 01/18] price fetching after migration --- .../DataProvider/PriceProviderFactory.swift | 6 +-- .../Sources/PriceDataSource.swift | 24 +++++++++--- .../PriceLocalStorageSubscriber.swift | 39 +++++++++++++++++-- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/fearless/Common/DataProvider/PriceProviderFactory.swift b/fearless/Common/DataProvider/PriceProviderFactory.swift index 2a9cb3c52..8cf614e5f 100644 --- a/fearless/Common/DataProvider/PriceProviderFactory.swift +++ b/fearless/Common/DataProvider/PriceProviderFactory.swift @@ -4,7 +4,7 @@ import SSFModels import SSFSingleValueCache protocol PriceProviderFactoryProtocol { - func getPricesProvider(currencies: [Currency]?) -> AnySingleValueProvider<[PriceData]> + func getPricesProvider(currencies: [Currency]?, chainAssets: [ChainAsset]) -> AnySingleValueProvider<[PriceData]> } final class PriceProviderFactory: PriceProviderFactoryProtocol { @@ -14,9 +14,9 @@ final class PriceProviderFactory: PriceProviderFactoryProtocol { return queue }() - func getPricesProvider(currencies: [Currency]?) -> AnySingleValueProvider<[SSFModels.PriceData]> { + func getPricesProvider(currencies: [Currency]?, chainAssets: [ChainAsset]) -> AnySingleValueProvider<[SSFModels.PriceData]> { let repository: CoreDataRepository = SingleValueCacheRepositoryFactoryDefault().createSingleValueCacheRepository() - let source = PriceDataSource(currencies: currencies) + let source = PriceDataSource(currencies: currencies, chainAssets: chainAssets) let trigger: DataProviderEventTrigger = [.onFetchPage] let provider = SingleValueProvider( targetIdentifier: PriceDataSource.defaultIdentifier, diff --git a/fearless/Common/DataProvider/Sources/PriceDataSource.swift b/fearless/Common/DataProvider/Sources/PriceDataSource.swift index d1990b896..e1e57260b 100644 --- a/fearless/Common/DataProvider/Sources/PriceDataSource.swift +++ b/fearless/Common/DataProvider/Sources/PriceDataSource.swift @@ -34,16 +34,22 @@ final class PriceDataSource: SingleValueProviderSourceProtocol { SoraSubqueryPriceFetcherDefault() }() - private lazy var chainAssets: [ChainAsset] = { - ChainRegistryFacade.sharedRegistry.availableChains.map { $0.chainAssets }.reduce([], +) - }() + private let chainRegistry = ChainRegistryFacade.sharedRegistry + + private lazy var chainAssets: [ChainAsset] = [] - init(currencies: [Currency]?) { + init(currencies: [Currency]?, chainAssets: [ChainAsset]) { self.currencies = currencies + self.chainAssets = chainAssets + setup() } func fetchOperation() -> CompoundOperationWrapper<[PriceData]?> { + guard chainAssets.isNotEmpty else { + return CompoundOperationWrapper.createWithResult([]) + } + let coingeckoOperation = createCoingeckoOperation() let chainlinkOperations = createChainlinkOperations() let soraSubqueryOperation = createSoraSubqueryOperation() @@ -58,7 +64,7 @@ final class PriceDataSource: SingleValueProviderSourceProtocol { let chainlinkPrices = chainlinkOperations.compactMap { try? $0.extractNoCancellableResultData() } - let soraSubqueryPrices = try soraSubqueryOperation.extractNoCancellableResultData() + let soraSubqueryPrices = (try? soraSubqueryOperation.extractNoCancellableResultData()) ?? [] prices = self.merge(coingeckoPrices: coingeckoPrices, chainlinkPrices: chainlinkPrices) prices = self.merge(coingeckoPrices: prices, soraSubqueryPrices: soraSubqueryPrices) @@ -160,6 +166,10 @@ final class PriceDataSource: SingleValueProviderSourceProtocol { } let chainAssets = chainAssets.filter { $0.asset.priceProvider?.type == .sorasubquery } + guard chainAssets.isNotEmpty else { + return BaseOperation.createWithResult([]) + } + let operation = soraOperationFactory.fetchPriceOperation(for: chainAssets) return operation } @@ -170,11 +180,15 @@ final class PriceDataSource: SingleValueProviderSourceProtocol { .map { $0.asset.coingeckoPriceId } .compactMap { $0 } .uniq(predicate: { $0 }) + guard priceIds.isNotEmpty else { + return BaseOperation.createWithResult([]) + } let operation = coingeckoOperationFactory.fetchPriceOperation(for: priceIds, currencies: currencies) return operation } private func createChainlinkOperations() -> [BaseOperation] { + return [] guard currencies?.count == 1, currencies?.first?.id == Currency.defaultCurrency().id else { return [] } diff --git a/fearless/Common/DataProvider/Subscription/PriceLocalStorageSubscriber.swift b/fearless/Common/DataProvider/Subscription/PriceLocalStorageSubscriber.swift index 303b3344a..47ae63aea 100644 --- a/fearless/Common/DataProvider/Subscription/PriceLocalStorageSubscriber.swift +++ b/fearless/Common/DataProvider/Subscription/PriceLocalStorageSubscriber.swift @@ -23,7 +23,7 @@ struct PriceLocalStorageSubscriberListener { final class PriceLocalStorageSubscriberImpl: PriceLocalStorageSubscriber { static let shared = PriceLocalStorageSubscriberImpl() - + private let eventCenter = EventCenter.shared private lazy var provider: AnySingleValueProvider<[PriceData]> = { setupProvider() }() @@ -39,7 +39,18 @@ final class PriceLocalStorageSubscriberImpl: PriceLocalStorageSubscriber { private var listeners: [PriceLocalStorageSubscriberListener] = [] private var sourcedCurrencies: Set = [] - private init() {} + private var chainAssets: [ChainAsset] = [] + private let chainsRepository: AsyncCoreDataRepositoryDefault + + private init() { + chainsRepository = ChainRepositoryFactory().createAsyncRepository() + setup() + } + + private func setup() { + eventCenter.add(observer: self) + refreshChainsAndSubscribe() + } // MARK: - PriceLocalStorageSubscriber @@ -118,7 +129,7 @@ final class PriceLocalStorageSubscriberImpl: PriceLocalStorageSubscriber { private func setupProvider() -> AnySingleValueProvider<[PriceData]> { let providerCurrencies = listeners.map { $0.currencies }.compactMap { $0 }.reduce([], +).uniq(predicate: { $0.id }) - let priceProvider = priceLocalSubscriber.getPricesProvider(currencies: providerCurrencies) + let priceProvider = priceLocalSubscriber.getPricesProvider(currencies: providerCurrencies, chainAssets: chainAssets) let updateClosure = { [weak self] (changes: [DataProviderChange<[PriceData]>]) in guard let prices: [PriceData] = changes.reduceToLastChange() else { @@ -232,4 +243,26 @@ final class PriceLocalStorageSubscriberImpl: PriceLocalStorageSubscriber { ) listeners.append(listener) } + + private func refreshChainsAndSubscribe() { + Task { + let chains = try await chainsRepository.fetchAll() + chainAssets = chains.map { $0.chainAssets }.reduce([], +) + + remoteFetchTimer?.invalidate() + remoteFetchTimer = nil + provider = setupProvider() + refreshProviderIfPossible() + } + } +} + +extension PriceLocalStorageSubscriberImpl: EventVisitorProtocol { + func processChainSyncDidComplete(event: ChainSyncDidComplete) { + guard event.newOrUpdatedChains.isNotEmpty else { + return + } + + refreshChainsAndSubscribe() + } } From ccaa9a63c0c6de6652ccc114b593b3cdd829f0c0 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Tue, 16 Jul 2024 15:50:47 +0700 Subject: [PATCH 02/18] crashfix --- .../ChainRegistry/ChainRegistry.swift | 5 ++ .../PolkaswapAdjustmentAssembly.swift | 2 +- .../PolkaswapAdjustmentPresenter.swift | 55 ++++++++++++++----- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/fearless/Common/Services/ChainRegistry/ChainRegistry.swift b/fearless/Common/Services/ChainRegistry/ChainRegistry.swift index ff3a5e531..e15388df5 100644 --- a/fearless/Common/Services/ChainRegistry/ChainRegistry.swift +++ b/fearless/Common/Services/ChainRegistry/ChainRegistry.swift @@ -17,6 +17,7 @@ protocol ChainRegistryProtocol: AnyObject { func getConnection(for chainId: ChainModel.Id) -> ChainConnection? func getRuntimeProvider(for chainId: ChainModel.Id) -> RuntimeProviderProtocol? func getChain(for chainId: ChainModel.Id) -> ChainModel? + func getChainUnsafe(for chainId: ChainModel.Id) -> ChainModel? func chainsSubscribe( _ target: AnyObject, runningInQueue: DispatchQueue, @@ -326,6 +327,10 @@ extension ChainRegistry: ChainRegistryProtocol { readLock.concurrentlyRead { chains.first(where: { $0.chainId == chainId }) } } + func getChainUnsafe(for chainId: ChainModel.Id) -> ChainModel? { + chains.first(where: { $0.chainId == chainId }) + } + func getRuntimeProvider(for chainId: ChainModel.Id) -> RuntimeProviderProtocol? { runtimeProviderPool.getRuntimeProvider(for: chainId) } diff --git a/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift b/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift index 9f7a06d7a..df32ea703 100644 --- a/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift +++ b/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift @@ -14,7 +14,7 @@ final class PolkaswapAdjustmentAssembly { let chainRegistry = ChainRegistryFacade.sharedRegistry guard - let xorChainAsset = chainRegistry.getChain(for: Chain.soraMain.genesisHash)?.utilityChainAssets().first, + let xorChainAsset = chainRegistry.getChainUnsafe(for: Chain.soraMain.genesisHash)?.utilityChainAssets().first, let connection = chainRegistry.getConnection(for: xorChainAsset.chain.chainId), let accountResponse = wallet.fetch(for: xorChainAsset.chain.accountRequest()), let runtimeService = chainRegistry.getRuntimeProvider(for: xorChainAsset.chain.chainId) diff --git a/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentPresenter.swift b/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentPresenter.swift index 4ba042a8f..375fe6109 100644 --- a/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentPresenter.swift +++ b/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentPresenter.swift @@ -43,7 +43,10 @@ final class PolkaswapAdjustmentPresenter { private var slippadgeTolerance: Float = Constants.slippadgeTolerance private var selectedLiquiditySourceType: LiquiditySourceType { didSet { - view?.didReceive(market: selectedLiquiditySourceType) + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.view?.didReceive(market: self.selectedLiquiditySourceType) + } } } @@ -103,14 +106,17 @@ final class PolkaswapAdjustmentPresenter { guard swapFromInputResult != nil || swapToInputResult != nil else { return } - - view?.setButtonLoadingState(isLoading: true) + DispatchQueue.main.async { [weak self] in + self?.view?.setButtonLoadingState(isLoading: true) + } loadingCollector.reset() } private func checkLoadingState() { if loadingCollector.isReady { - view?.setButtonLoadingState(isLoading: false) + DispatchQueue.main.async { [weak self] in + self?.view?.setButtonLoadingState(isLoading: false) + } } } @@ -140,9 +146,11 @@ final class PolkaswapAdjustmentPresenter { .createBalanceInputViewModel(inputAmount) .value(for: selectedLocale) - view?.didReceiveSwapFrom(viewModel: viewModel) - if updateAmountInput { - view?.didReceiveSwapFrom(amountInputViewModel: inputViewModel) + DispatchQueue.main.async { [weak self] in + self?.view?.didReceiveSwapFrom(viewModel: viewModel) + if updateAmountInput { + self?.view?.didReceiveSwapFrom(amountInputViewModel: inputViewModel) + } } loadingCollector.fromReady = true @@ -171,9 +179,11 @@ final class PolkaswapAdjustmentPresenter { .createBalanceInputViewModel(inputAmount) .value(for: selectedLocale) - view?.didReceiveSwapTo(viewModel: viewModel) - if updateAmountInput { - view?.didReceiveSwapTo(amountInputViewModel: inputViewModel) + DispatchQueue.main.async { [weak self] in + self?.view?.didReceiveSwapTo(viewModel: viewModel) + if updateAmountInput { + self?.view?.didReceiveSwapTo(amountInputViewModel: inputViewModel) + } } loadingCollector.toReady = true @@ -300,7 +310,10 @@ final class PolkaswapAdjustmentPresenter { prices: prices, locale: selectedLocale ) - view?.didReceiveDetails(viewModel: detailsViewModel) + + DispatchQueue.main.async { [weak self] in + self?.view?.didReceiveDetails(viewModel: detailsViewModel) + } loadingCollector.detailsReady = true checkLoadingState() @@ -317,7 +330,11 @@ final class PolkaswapAdjustmentPresenter { swapFromInputResult = .absolute(amounts.toAmount) provideFromAssetVewModel() } - view?.didReceive(variant: swapVariant) + + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.view?.didReceive(variant: self.swapVariant) + } } private func fetchSwapFee(amounts: SwapQuoteAmounts) { @@ -377,7 +394,10 @@ final class PolkaswapAdjustmentPresenter { swapToInputResult = nil provideFromAssetVewModel() provideToAssetVewModel() - view?.didReceiveDetails(viewModel: nil) + + DispatchQueue.main.async { [weak self] in + self?.view?.didReceiveDetails(viewModel: nil) + } } private func preparePreviewParams() -> PolkaswapPreviewParams? { @@ -936,7 +956,10 @@ extension PolkaswapAdjustmentPresenter: SelectAssetModuleOutput { extension PolkaswapAdjustmentPresenter: PolkaswapTransaktionSettingsModuleOutput { func didReceive(market: LiquiditySourceType, slippadgeTolerance: Float) { loadingCollector.detailsReady = false - view?.setButtonLoadingState(isLoading: true) + + DispatchQueue.main.async { [weak self] in + self?.view?.setButtonLoadingState(isLoading: true) + } if selectedLiquiditySourceType != market { selectedLiquiditySourceType = market @@ -959,6 +982,8 @@ extension PolkaswapAdjustmentPresenter: BannersModuleOutput { func reloadBannersView() {} func didTapCloseBanners() { - view?.hideBanners() + DispatchQueue.main.async { [weak self] in + self?.view?.hideBanners() + } } } From 149036986be791d98cbc555e529585ca16ac8ba8 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Thu, 18 Jul 2024 13:16:40 +0700 Subject: [PATCH 03/18] account stats fetching, account stats module, localization --- Podfile | 2 +- Podfile.lock | 8 +- fearless.xcodeproj/project.pbxproj | 267 ++++++++---------- .../xcshareddata/swiftpm/Package.resolved | 27 +- .../Alchemy/AlchemyRequest.swift | 1 + .../Alchemy/AlchemyService.swift | 3 +- .../Pricing/SoraSubqueryPriceFetcher.swift | 3 +- .../AccountStatisticsFetching.swift | 9 + .../Nomis/NomisAccountStatisticsFetcher.swift | 39 +++ .../Nomis/NomisRequestSigner.swift | 13 + .../NomisAccountStatisticsRequest.swift | 34 +++ .../Onboarding/OnboardingService.swift | 2 +- .../GiantsquidStakingRewardsFetcher.swift | 3 +- .../ReefStakingRewardsFetcher.swift | 3 +- .../Requests/StakingRewardsRequest.swift | 1 + .../SoraStakingRewardsFetcher.swift | 3 +- .../SubqueryStakingRewardsFetcher.swift | 3 +- .../SubsquidStakingRewardsFetcher.swift | 3 +- fearless/Common/Model/AccountStatistics.swift | 21 ++ .../Network/NetworkClientFactory.swift | 16 -- .../Network/RequestConfiguratorFactory.swift | 22 -- .../Network/RequestSignerFactory.swift | 22 -- .../Network/ResponseDecodersFactory.swift | 17 -- .../Network/Config/NetworkClientType.swift | 6 - .../Network/Config/NetworkRequestType.swift | 7 - .../Network/Config/RequestConfig.swift | 34 --- .../Network/Config/RequestSigningType.swift | 7 - .../Network/Config/ResponseDecoderType.swift | 6 - .../Network/Models/HTTPHeader.swift | 6 - .../Network/Models/HTTPMethod.swift | 6 - .../Network/Models/NetworkingError.swift | 209 -------------- .../Network/NetworkClient/NetworkClient.swift | 5 - .../NetworkClient/RESTNetworkClient.swift | 48 ---- .../Network/NetworkWorker.swift | 22 -- .../RESTRequestConfigurator.swift | 52 ---- .../RequestConfigurator.swift | 5 - .../RequestSigners/AlchemyRequestSigner.swift | 23 -- .../RequestSigners/RequestSigner.swift | 9 - .../JSONResponseDecoder.swift | 28 -- .../ResponseDecoders/ResponseDecoder.swift | 5 - .../AccountStatisticsAssembly.swift | 24 ++ .../AccountStatisticsInteractor.swift | 17 ++ .../AccountStatisticsPresenter.swift | 51 ++++ .../AccountStatisticsProtocols.swift | 10 + .../AccountStatisticsRouter.swift | 3 + .../AccountStatisticsViewController.swift | 55 ++++ .../AccountStatisticsViewLayout.swift | 175 ++++++++++++ .../Resources/chains.json | 4 +- .../WalletMainContainerAssembly.swift | 6 +- .../WalletMainContainerInteractor.swift | 25 +- fearless/en.lproj/Localizable.strings | 16 +- fearless/id.lproj/Localizable.strings | 16 +- fearless/ja.lproj/Localizable.strings | 16 +- fearless/pt.lproj/Localizable.strings | 14 + fearless/ru.lproj/Localizable.strings | 56 ++-- fearless/tr.lproj/Localizable.strings | 16 +- fearless/vi.lproj/Localizable.strings | 62 ++-- fearless/zh-Hans.lproj/Localizable.strings | 16 +- .../AccountStatisticsTests.swift | 16 ++ 59 files changed, 818 insertions(+), 780 deletions(-) create mode 100644 fearless/ApplicationLayer/Services/AccountStatistics/AccountStatisticsFetching.swift create mode 100644 fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift create mode 100644 fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisRequestSigner.swift create mode 100644 fearless/ApplicationLayer/Services/AccountStatistics/Nomis/Request/NomisAccountStatisticsRequest.swift create mode 100644 fearless/Common/Model/AccountStatistics.swift delete mode 100644 fearless/CoreLayer/ComponentFactories/Network/NetworkClientFactory.swift delete mode 100644 fearless/CoreLayer/ComponentFactories/Network/RequestConfiguratorFactory.swift delete mode 100644 fearless/CoreLayer/ComponentFactories/Network/RequestSignerFactory.swift delete mode 100644 fearless/CoreLayer/ComponentFactories/Network/ResponseDecodersFactory.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/Config/NetworkClientType.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/Config/NetworkRequestType.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/Config/RequestConfig.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/Config/RequestSigningType.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/Config/ResponseDecoderType.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/Models/HTTPHeader.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/Models/HTTPMethod.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/Models/NetworkingError.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/NetworkClient/NetworkClient.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/NetworkClient/RESTNetworkClient.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/NetworkWorker.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/RequestConfigurators/RESTRequestConfigurator.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/RequestConfigurators/RequestConfigurator.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/RequestSigners/AlchemyRequestSigner.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/RequestSigners/RequestSigner.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/ResponseDecoders/JSONResponseDecoder.swift delete mode 100644 fearless/CoreLayer/CoreComponents/Network/ResponseDecoders/ResponseDecoder.swift create mode 100644 fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift create mode 100644 fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift create mode 100644 fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift create mode 100644 fearless/Modules/AccountStatistics/AccountStatisticsProtocols.swift create mode 100644 fearless/Modules/AccountStatistics/AccountStatisticsRouter.swift create mode 100644 fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift create mode 100644 fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift create mode 100644 fearlessTests/Modules/AccountStatistics/AccountStatisticsTests.swift diff --git a/Podfile b/Podfile index bf5de581d..50b3bcdf3 100644 --- a/Podfile +++ b/Podfile @@ -20,7 +20,7 @@ abstract_target 'fearlessAll' do pod 'SVGKit' pod 'Charts', '~> 4.1.0' pod 'MediaView', :git => 'https://github.com/bnsports/MediaView.git', :branch => 'dev' - pod 'FearlessKeys', '0.1.3' + pod 'FearlessKeys', '0.1.4' target 'fearlessTests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock index 450eab24d..a1956dcbf 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -9,7 +9,7 @@ PODS: - Cuckoo (1.10.4): - Cuckoo/Swift (= 1.10.4) - Cuckoo/Swift (1.10.4) - - FearlessKeys (0.1.3) + - FearlessKeys (0.1.4) - FireMock (3.1) - Kingfisher (7.10.2) - MediaView (0.2.0) @@ -82,7 +82,7 @@ PODS: DEPENDENCIES: - Charts (~> 4.1.0) - Cuckoo - - FearlessKeys (= 0.1.3) + - FearlessKeys (= 0.1.4) - FireMock - Kingfisher (= 7.10.2) - MediaView (from `https://github.com/bnsports/MediaView.git`, branch `dev`) @@ -140,7 +140,7 @@ SPEC CHECKSUMS: Charts: ce0768268078eee0336f122c3c4ca248e4e204c5 CocoaLumberjack: df59726690390bb8aaaa585938564ba1c8dbbb44 Cuckoo: 20b8aed94022e0e43e90f7c9e4fb0c86f0926a01 - FearlessKeys: c86874e021dbc11c035f68a83f35569cd21d2e6c + FearlessKeys: 5ec2782533624d237c899677a8c10859fbbc6668 FireMock: 3eed872059c12f94855413347da83b9d6d1a6fac Kingfisher: 99edc495d3b7607e6425f0d6f6847b2abd6d716d MediaView: 10ff6a5c7950a7c72c5da9e9b89cc85a981e6abc @@ -158,6 +158,6 @@ SPEC CHECKSUMS: SwiftLint: c1de071d9d08c8aba837545f6254315bc900e211 SwiftyBeaver: 014b0c12065026b731bac80305294f27d63e27f6 -PODFILE CHECKSUM: 934ddb815cb022338f1abd75110a1a634cde7340 +PODFILE CHECKSUM: 6eca9a23a0e78699b9b76e0f4a5d70c067f5290f COCOAPODS: 1.15.2 diff --git a/fearless.xcodeproj/project.pbxproj b/fearless.xcodeproj/project.pbxproj index 97062d380..9f7807bc4 100644 --- a/fearless.xcodeproj/project.pbxproj +++ b/fearless.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 002561414AF1F8F3B4B65538 /* WalletTransactionDetailsWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7416F3AFA5F4D1130B1C410 /* WalletTransactionDetailsWireframe.swift */; }; 0077B6854FDCA7ECCD557B09 /* StakingPoolCreateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D651E438F86F37CC07D6D3F /* StakingPoolCreateViewController.swift */; }; + 00E9DD69FCE94A4F4929B419 /* AccountStatisticsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF646F59913E95891915BDC /* AccountStatisticsProtocols.swift */; }; 02EC6059AEE8C92B4EDD09C0 /* LiquidityPoolsOverviewViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC0B2D0A77F4B0F7CC9E7C1D /* LiquidityPoolsOverviewViewLayout.swift */; }; 02FA6FDF7212F1F8D056BC18 /* SwapTransactionDetailAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = C597BAB74DF23914B68FDC39 /* SwapTransactionDetailAssembly.swift */; }; 04A17615051F4A1AE0E63BF8 /* PolkaswapSwapConfirmationViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F67BB672040911E4A165446 /* PolkaswapSwapConfirmationViewLayout.swift */; }; @@ -232,6 +233,7 @@ 1062C095BC566A1EA8DE1C06 /* CrowdloanContributionSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C71DEF78B69F017DF460AB7 /* CrowdloanContributionSetupViewController.swift */; }; 10B4951F5E0C515EFBDBC32E /* StakingPoolCreateConfirmPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3837CE5CB2D48D8A694A7EE0 /* StakingPoolCreateConfirmPresenter.swift */; }; 10DEF797CB3DC5BF0903EC4C /* LiquidityPoolRemoveLiquidityConfirmViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3459F610D6E5C782D8695A9 /* LiquidityPoolRemoveLiquidityConfirmViewLayout.swift */; }; + 11FDC274784B05B690368C07 /* AccountStatisticsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB86E65E22C0AF7EDD0701A4 /* AccountStatisticsPresenter.swift */; }; 134AFD616BE52A1AE290EEF7 /* StakingBalanceFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C67ACC943DA07FC529AE69B4 /* StakingBalanceFlow.swift */; }; 135CEEC5363BE34130958578 /* ControllerAccountConfirmationInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B8473AA386E1AD6F0F0C964 /* ControllerAccountConfirmationInteractor.swift */; }; 1496E87A7652C7D230A9BB46 /* AssetNetworksRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36A3D64FF58C40E5CC6A6E89 /* AssetNetworksRouter.swift */; }; @@ -287,6 +289,7 @@ 2AD0A19525D3D3EC00312428 /* GitHubOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD0A19425D3D3EC00312428 /* GitHubOperationFactory.swift */; }; 2B0FC94B4AE9AFE9532F493F /* ReferralCrowdloanViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71285CF636B32ACD8EB5519E /* ReferralCrowdloanViewFactory.swift */; }; 2B99F241DC91645B1226E10C /* WarningAlertWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 639AD37D87BD08106E7E6E2A /* WarningAlertWireframe.swift */; }; + 2BBE065C2A5C31B830DE0957 /* AccountStatisticsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C85B1F841C281165D7AACB1 /* AccountStatisticsViewController.swift */; }; 2C3124A5EBC1AD57C01EEA17 /* SelectValidatorsStartInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFED3DAA18BCEF0BFA15728 /* SelectValidatorsStartInteractor.swift */; }; 2CF2F93AF862CF54FC46B560 /* PurchaseInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D44421CCD7AD220A05CD0E /* PurchaseInteractor.swift */; }; 2CFEBF8B7B9C820D1A80B60B /* StakingPoolCreateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A6A52B8A4D734D5BCADE355 /* StakingPoolCreateTests.swift */; }; @@ -369,6 +372,7 @@ 51876200A6B1EDC54609DF46 /* LiquidityPoolRemoveLiquidityProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D49CA5CB156C1EA38BEBE00 /* LiquidityPoolRemoveLiquidityProtocols.swift */; }; 51FC48FA6FD4D2FB1781424D /* ReferralCrowdloanWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D51D60F19284936A6E9F47D /* ReferralCrowdloanWireframe.swift */; }; 525CCCA7CFD7BE570AD0FCCA /* WalletOptionProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73078ED3B2642FEAF348DB2A /* WalletOptionProtocols.swift */; }; + 52AEA30073F8CB856B692757 /* AccountStatisticsViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7743EA304BC53649D0473225 /* AccountStatisticsViewLayout.swift */; }; 539340533D8383965751C6D8 /* NodeSelectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0CF2F98779D3C18D0C0A29 /* NodeSelectionTests.swift */; }; 53DA09F488806FFE86C841AA /* SelectMarketInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB837A15BAAED64BC32F3F44 /* SelectMarketInteractor.swift */; }; 54A3B34605E787B47741ED1A /* Pods_fearlessAll_fearlessIntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C323602A21644DCB1B2EEFF6 /* Pods_fearlessAll_fearlessIntegrationTests.framework */; }; @@ -388,6 +392,7 @@ 5DDD2206DF795CF205610455 /* AccountExportPasswordPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31226053044986BC828AA912 /* AccountExportPasswordPresenter.swift */; }; 5E8504507116E0177D70314B /* LiquidityPoolSupplyViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438E01C5C877428168E9F3F8 /* LiquidityPoolSupplyViewLayout.swift */; }; 5E9402965D385607E04156DC /* NftDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DEDC1A0ED429DD43EC621E /* NftDetailsPresenter.swift */; }; + 5E974C26655D3E64AD6A923D /* AccountStatisticsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0542BF70B1BADBF1459D57FB /* AccountStatisticsInteractor.swift */; }; 5F5825D27863628412B672CA /* NftSendConfirmRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 803E71983CD61FFBFE98DA7A /* NftSendConfirmRouter.swift */; }; 607699C7CEEDA3598613DCA0 /* NetworkInfoViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EDA19BC3280F1838C687EC8 /* NetworkInfoViewFactory.swift */; }; 60C22E112CA857A2EA5A129E /* LiquidityPoolsOverviewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC76E7D99A98423180BC572F /* LiquidityPoolsOverviewTests.swift */; }; @@ -1313,8 +1318,10 @@ 85B1B7387F09A8405C4E688A /* WalletSendConfirmTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AF4258723E2FACBBA556D00 /* WalletSendConfirmTests.swift */; }; 872DF7DE5A001DF5B8A4E288 /* StakingBondMoreConfirmationFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BA5883C1103D3A2218D839 /* StakingBondMoreConfirmationFlow.swift */; }; 87C1FC2909A8360DDBA625E5 /* LiquidityPoolSupplyInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA528679A82B9A327853804 /* LiquidityPoolSupplyInteractor.swift */; }; + 8852522BE02B6244A00E85A1 /* AccountStatisticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26222D0DBE5CEF0BA7DCCEF7 /* AccountStatisticsTests.swift */; }; 885551F78A5926D16D5AF0CB /* ControllerAccountPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E5CB64B91B35804B3671456 /* ControllerAccountPresenter.swift */; }; 886E8CF81EF2566D98D9693E /* ExportSeedViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FA66143B25AA70B02CE461 /* ExportSeedViewFactory.swift */; }; + 887CE12C7C59F5DB092E9227 /* AccountStatisticsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD63BEB84A28855006BE680 /* AccountStatisticsRouter.swift */; }; 887DCCC498EB8472021DCE3E /* PolkaswapSwapConfirmationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC50F2FF6E8EBC00B56CB86D /* PolkaswapSwapConfirmationRouter.swift */; }; 888D852FAE0318FAE4A31252 /* PolkaswapAdjustmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AAC6AAD532FC7E63765D85 /* PolkaswapAdjustmentViewController.swift */; }; 88F3A9FB9CEA464275F1115E /* ExportMnemonicViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47759907380BE9300E54DC78 /* ExportMnemonicViewFactory.swift */; }; @@ -1332,6 +1339,7 @@ 8CDA490B390BFA261906F8FC /* CrowdloanContributionSetupViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23BC71941B91D3E372CDB11C /* CrowdloanContributionSetupViewLayout.swift */; }; 8D9BC9C36DC891CDD900A895 /* AccountConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3104ABC4BECF08B0BA836AA /* AccountConfirmViewController.swift */; }; 8F0B3FE843167777A1D3771C /* NodeSelectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B3E1906EEBE32E71E82BB6 /* NodeSelectionViewLayout.swift */; }; + 8FC70700D2154F472636D458 /* AccountStatisticsAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62CD1B83902C1B5763476EFF /* AccountStatisticsAssembly.swift */; }; 9081D43697D992F51E057ED2 /* CrowdloanContributionSetupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F7068913923A6DEEE9D8EA0 /* CrowdloanContributionSetupPresenter.swift */; }; 90A3F46EF181DC2B821CC80C /* CrowdloanContributionConfirmViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F791FE1B479CE1DF936F79F /* CrowdloanContributionConfirmViewFactory.swift */; }; 90EFE3768F1375470FDBE6F6 /* PurchaseViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AA3BF0C9C1E0E2C67D962F5 /* PurchaseViewFactory.swift */; }; @@ -2224,6 +2232,7 @@ FA4441342BF75FD90067C633 /* LiquidityPoolListType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4441332BF75FD90067C633 /* LiquidityPoolListType.swift */; }; FA46D2C7283DDD07005A112B /* ParachainStakingCandidateMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA46D2C6283DDD07005A112B /* ParachainStakingCandidateMetadata.swift */; }; FA4889672B7F5E360092ABF8 /* GiantsquidExtrinsic.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4889662B7F5E360092ABF8 /* GiantsquidExtrinsic.swift */; }; + FA4B098E2C47804F001B73F9 /* NomisRequestSigner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4B098D2C47804F001B73F9 /* NomisRequestSigner.swift */; }; FA4B928F284493C60003BCEF /* DelegateCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4B928E284493C60003BCEF /* DelegateCall.swift */; }; FA4B92912844CF750003BCEF /* MetaAccountModelChangedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4B92902844CF750003BCEF /* MetaAccountModelChangedEvent.swift */; }; FA4B92932844D0060003BCEF /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4B92922844D0060003BCEF /* Currency.swift */; }; @@ -2403,27 +2412,6 @@ FA72546B2AC2F12D00EC47A6 /* WalletConnectNetworking in Frameworks */ = {isa = PBXBuildFile; productRef = FA72546A2AC2F12D00EC47A6 /* WalletConnectNetworking */; }; FA72546D2AC2F12D00EC47A6 /* WalletConnectPairing in Frameworks */ = {isa = PBXBuildFile; productRef = FA72546C2AC2F12D00EC47A6 /* WalletConnectPairing */; }; FA72546F2AC2F12D00EC47A6 /* Web3Wallet in Frameworks */ = {isa = PBXBuildFile; productRef = FA72546E2AC2F12D00EC47A6 /* Web3Wallet */; }; - FA7336C12A0E3B7F0096A291 /* NetworkClientFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336BD2A0E3B7F0096A291 /* NetworkClientFactory.swift */; }; - FA7336C22A0E3B7F0096A291 /* RequestConfiguratorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336BE2A0E3B7F0096A291 /* RequestConfiguratorFactory.swift */; }; - FA7336C32A0E3B7F0096A291 /* RequestSignerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336BF2A0E3B7F0096A291 /* RequestSignerFactory.swift */; }; - FA7336C42A0E3B7F0096A291 /* ResponseDecodersFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336C02A0E3B7F0096A291 /* ResponseDecodersFactory.swift */; }; - FA7336D92A0E3B880096A291 /* RESTRequestConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336C72A0E3B870096A291 /* RESTRequestConfigurator.swift */; }; - FA7336DA2A0E3B880096A291 /* RequestConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336C82A0E3B870096A291 /* RequestConfigurator.swift */; }; - FA7336DB2A0E3B880096A291 /* NetworkWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336C92A0E3B870096A291 /* NetworkWorker.swift */; }; - FA7336DC2A0E3B880096A291 /* RequestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336CB2A0E3B870096A291 /* RequestConfig.swift */; }; - FA7336DD2A0E3B880096A291 /* ResponseDecoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336CC2A0E3B870096A291 /* ResponseDecoderType.swift */; }; - FA7336DE2A0E3B880096A291 /* RequestSigningType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336CD2A0E3B870096A291 /* RequestSigningType.swift */; }; - FA7336DF2A0E3B880096A291 /* NetworkRequestType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336CE2A0E3B870096A291 /* NetworkRequestType.swift */; }; - FA7336E02A0E3B880096A291 /* NetworkClientType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336CF2A0E3B870096A291 /* NetworkClientType.swift */; }; - FA7336E12A0E3B880096A291 /* JSONResponseDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336D12A0E3B870096A291 /* JSONResponseDecoder.swift */; }; - FA7336E22A0E3B880096A291 /* ResponseDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336D22A0E3B870096A291 /* ResponseDecoder.swift */; }; - FA7336E32A0E3B880096A291 /* AlchemyRequestSigner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336D42A0E3B870096A291 /* AlchemyRequestSigner.swift */; }; - FA7336E42A0E3B880096A291 /* RequestSigner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336D52A0E3B870096A291 /* RequestSigner.swift */; }; - FA7336E52A0E3B880096A291 /* NetworkClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336D72A0E3B870096A291 /* NetworkClient.swift */; }; - FA7336E62A0E3B880096A291 /* RESTNetworkClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336D82A0E3B870096A291 /* RESTNetworkClient.swift */; }; - FA7336E92A0E3CCC0096A291 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336E82A0E3CCC0096A291 /* HTTPMethod.swift */; }; - FA7336EB2A0E3D0A0096A291 /* HTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336EA2A0E3D0A0096A291 /* HTTPHeader.swift */; }; - FA7336F82A0E3E1B0096A291 /* NetworkingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336F72A0E3E1B0096A291 /* NetworkingError.swift */; }; FA7336FD2A132F740096A291 /* AlchemyHistoryOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7336FC2A132F740096A291 /* AlchemyHistoryOperationFactory.swift */; }; FA7337092A1339890096A291 /* AssetTransactionData+AlchemyHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7337082A1339890096A291 /* AssetTransactionData+AlchemyHistory.swift */; }; FA74359529C0733E0085A47E /* Array+Difference.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA74359429C0733E0085A47E /* Array+Difference.swift */; }; @@ -2985,6 +2973,10 @@ FAD429362A8656B7001D6A16 /* UICollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD429342A8656B7001D6A16 /* UICollectionView.swift */; }; FAD558CB298933F8008AA5A8 /* ChainModelGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F13F0F26F1DC43006725FF /* ChainModelGenerator.swift */; }; FAD558CC298A17A1008AA5A8 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842D1E8F24D20B1500C30A7A /* Constants.swift */; }; + FAD5FF252C463C07003201F5 /* AccountStatisticsFetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD5FF242C463C07003201F5 /* AccountStatisticsFetching.swift */; }; + FAD5FF272C463C4F003201F5 /* AccountStatistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD5FF262C463C4F003201F5 /* AccountStatistics.swift */; }; + FAD5FF2B2C46464B003201F5 /* NomisAccountStatisticsFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD5FF2A2C46464B003201F5 /* NomisAccountStatisticsFetcher.swift */; }; + FAD5FF2E2C464717003201F5 /* NomisAccountStatisticsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD5FF2D2C464717003201F5 /* NomisAccountStatisticsRequest.swift */; }; FAD646C2284DD2CF007CCB92 /* StakingBalanceRelaychainStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD646C1284DD2CF007CCB92 /* StakingBalanceRelaychainStrategy.swift */; }; FAD646C4284DD2DA007CCB92 /* StakingBalanceRelaychainViewModelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD646C3284DD2DA007CCB92 /* StakingBalanceRelaychainViewModelState.swift */; }; FAD646C6284DD2E5007CCB92 /* StakingBalanceRelaychainViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD646C5284DD2E5007CCB92 /* StakingBalanceRelaychainViewModelFactory.swift */; }; @@ -3041,6 +3033,7 @@ FAF5E9DC27E46DAA005A3448 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF5E9DA27E46DAA005A3448 /* RootViewController.swift */; }; FAF5E9DE27E46DCC005A3448 /* String+VersionComparsion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF5E9DD27E46DCC005A3448 /* String+VersionComparsion.swift */; }; FAF5E9E127E4A4C1005A3448 /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF5E9E027E4A4C1005A3448 /* RootViewModel.swift */; }; + FAF600752C48D79600E56558 /* Cosmos in Frameworks */ = {isa = PBXBuildFile; productRef = FAF600742C48D79600E56558 /* Cosmos */; }; FAF92E6627B4275F005467CE /* Bool+ToInt.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF92E6527B4275E005467CE /* Bool+ToInt.swift */; }; FAF96B582B636FC700E299C1 /* SystemNumberRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF96B572B636FC700E299C1 /* SystemNumberRequest.swift */; }; FAF9C2962AAADE3300A61D21 /* DeprecatedControllerStashAccountCheckService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF9C2952AAADE3300A61D21 /* DeprecatedControllerStashAccountCheckService.swift */; }; @@ -3156,6 +3149,7 @@ 04806331BF10F63A49326941 /* NetworkInfoWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NetworkInfoWireframe.swift; sourceTree = ""; }; 04EF69DFE142600FF2708A13 /* ControllerAccountConfirmationViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControllerAccountConfirmationViewController.swift; sourceTree = ""; }; 0533F9E531CDFB721D697769 /* YourValidatorListPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YourValidatorListPresenter.swift; sourceTree = ""; }; + 0542BF70B1BADBF1459D57FB /* AccountStatisticsInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountStatisticsInteractor.swift; sourceTree = ""; }; 06F6B892F62579DE761073CA /* LiquidityPoolSupplyConfirmPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LiquidityPoolSupplyConfirmPresenter.swift; sourceTree = ""; }; 0702B31429701759003519F5 /* AmountInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountInputViewModel.swift; sourceTree = ""; }; 0702B31729701864003519F5 /* WalletViewModelObserverContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletViewModelObserverContainer.swift; sourceTree = ""; }; @@ -3406,6 +3400,7 @@ 25B80FDDB5C3032A0BBBD826 /* NftCollectionPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftCollectionPresenter.swift; sourceTree = ""; }; 25D9454047EBBD8D8A0174A4 /* LiquidityPoolRemoveLiquidityRouter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LiquidityPoolRemoveLiquidityRouter.swift; sourceTree = ""; }; 25FF82C2FD912021A1F20876 /* PolkaswapAdjustmentViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PolkaswapAdjustmentViewLayout.swift; sourceTree = ""; }; + 26222D0DBE5CEF0BA7DCCEF7 /* AccountStatisticsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountStatisticsTests.swift; sourceTree = ""; }; 262F98DEF54FA9592BE22B94 /* AllDoneAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AllDoneAssembly.swift; sourceTree = ""; }; 2648EEF96694A7FEC94520E8 /* WalletHistoryFilterTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletHistoryFilterTests.swift; sourceTree = ""; }; 26635BD49FBF19DB1253906E /* NetworkInfoTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NetworkInfoTests.swift; sourceTree = ""; }; @@ -3499,6 +3494,7 @@ 49C564052FAA3160AA8975CB /* NftSendAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftSendAssembly.swift; sourceTree = ""; }; 49EBB77A32A59568B0DACFE5 /* StakingPoolCreateProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingPoolCreateProtocols.swift; sourceTree = ""; }; 4B3B14C046584AAAF483715F /* WalletTransactionHistoryWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletTransactionHistoryWireframe.swift; sourceTree = ""; }; + 4BF646F59913E95891915BDC /* AccountStatisticsProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountStatisticsProtocols.swift; sourceTree = ""; }; 4C0FA282377DCAB7C59ACFB6 /* RecommendedValidatorListViewController.xib */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.xib; path = RecommendedValidatorListViewController.xib; sourceTree = ""; }; 4C5EF68BE0E29D2305CB7337 /* UsernameSetupTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UsernameSetupTests.swift; sourceTree = ""; }; 4C71DEF78B69F017DF460AB7 /* CrowdloanContributionSetupViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CrowdloanContributionSetupViewController.swift; sourceTree = ""; }; @@ -3558,6 +3554,7 @@ 61EBE466BDCF77E65FDCDF81 /* ExportMnemonicPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExportMnemonicPresenter.swift; sourceTree = ""; }; 6216F6F1B91F798F07695FB6 /* StakingAmountWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingAmountWireframe.swift; sourceTree = ""; }; 62928FB3556EEA3A228131AC /* LiquidityPoolSupplyAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LiquidityPoolSupplyAssembly.swift; sourceTree = ""; }; + 62CD1B83902C1B5763476EFF /* AccountStatisticsAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountStatisticsAssembly.swift; sourceTree = ""; }; 62FA66143B25AA70B02CE461 /* ExportSeedViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExportSeedViewFactory.swift; sourceTree = ""; }; 638A65DAC86BAF9EB4D2F2F8 /* StakingRewardDetailsWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingRewardDetailsWireframe.swift; sourceTree = ""; }; 639AD37D87BD08106E7E6E2A /* WarningAlertWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WarningAlertWireframe.swift; sourceTree = ""; }; @@ -3599,6 +3596,7 @@ 748E0AF1A286016CB220155C /* ControllerAccountInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ControllerAccountInteractor.swift; sourceTree = ""; }; 75B53E901B1475DE858A2C99 /* ContactsRouter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ContactsRouter.swift; sourceTree = ""; }; 761FDEBB414B1CFAD6992352 /* AnalyticsRewardDetailsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AnalyticsRewardDetailsTests.swift; sourceTree = ""; }; + 7743EA304BC53649D0473225 /* AccountStatisticsViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountStatisticsViewLayout.swift; sourceTree = ""; }; 779702BC0E9C9882BEA5C273 /* StakingPoolCreateConfirmInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingPoolCreateConfirmInteractor.swift; sourceTree = ""; }; 782CC21A2F9EEF5DBA3AB1AA /* PurchaseProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PurchaseProtocols.swift; sourceTree = ""; }; 784D20E16EEE55C2CF7B319B /* StakingBondMoreFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingBondMoreFlow.swift; sourceTree = ""; }; @@ -3607,6 +3605,7 @@ 7BCAF4A12D0F22D3C9035A1A /* WarningAlertPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WarningAlertPresenter.swift; sourceTree = ""; }; 7BDBADCF78FB10BE08DE5259 /* Pods-fearlessTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-fearlessTests.release.xcconfig"; path = "Target Support Files/Pods-fearlessTests/Pods-fearlessTests.release.xcconfig"; sourceTree = ""; }; 7C70EBF83B2547452417E588 /* StakingRewardDetailsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingRewardDetailsViewController.swift; sourceTree = ""; }; + 7C85B1F841C281165D7AACB1 /* AccountStatisticsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountStatisticsViewController.swift; sourceTree = ""; }; 7CCB1AFB751075497345C3E7 /* ClaimCrowdloanRewardsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ClaimCrowdloanRewardsViewController.swift; sourceTree = ""; }; 7DDDB2B35CD3299F50613141 /* ReferralCrowdloanViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferralCrowdloanViewController.swift; sourceTree = ""; }; 7DE7B3D0BE06472153C0A78C /* NftCollectionAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftCollectionAssembly.swift; sourceTree = ""; }; @@ -4702,6 +4701,7 @@ BB5E8FAB4C12D7BFEEF576AD /* AnalyticsRewardDetailsWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AnalyticsRewardDetailsWireframe.swift; sourceTree = ""; }; BB719C069A26244D194C4374 /* WalletChainAccountDashboardViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletChainAccountDashboardViewFactory.swift; sourceTree = ""; }; BB837A15BAAED64BC32F3F44 /* SelectMarketInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectMarketInteractor.swift; sourceTree = ""; }; + BB86E65E22C0AF7EDD0701A4 /* AccountStatisticsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountStatisticsPresenter.swift; sourceTree = ""; }; BC2C9D26B9F9CC048C67796F /* AnalyticsRewardDetailsViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AnalyticsRewardDetailsViewLayout.swift; sourceTree = ""; }; BE7D061906CF67230BF5393A /* NftSendConfirmTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftSendConfirmTests.swift; sourceTree = ""; }; C0EE58376751B23A9CEAEE1A /* StakingPoolCreateConfirmRouter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StakingPoolCreateConfirmRouter.swift; sourceTree = ""; }; @@ -5388,6 +5388,7 @@ FA4441332BF75FD90067C633 /* LiquidityPoolListType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiquidityPoolListType.swift; sourceTree = ""; }; FA46D2C6283DDD07005A112B /* ParachainStakingCandidateMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParachainStakingCandidateMetadata.swift; sourceTree = ""; }; FA4889662B7F5E360092ABF8 /* GiantsquidExtrinsic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiantsquidExtrinsic.swift; sourceTree = ""; }; + FA4B098D2C47804F001B73F9 /* NomisRequestSigner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NomisRequestSigner.swift; sourceTree = ""; }; FA4B928E284493C60003BCEF /* DelegateCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateCall.swift; sourceTree = ""; }; FA4B92902844CF750003BCEF /* MetaAccountModelChangedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetaAccountModelChangedEvent.swift; sourceTree = ""; }; FA4B92922844D0060003BCEF /* Currency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Currency.swift; sourceTree = ""; }; @@ -5565,27 +5566,6 @@ FA72541D2AC2E48400EC47A6 /* WalletConnectService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectService.swift; sourceTree = ""; }; FA72541E2AC2E48400EC47A6 /* WalletConnectSocketFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectSocketFactory.swift; sourceTree = ""; }; FA72541F2AC2E48500EC47A6 /* WalletConnectPayloadFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectPayloadFactory.swift; sourceTree = ""; }; - FA7336BD2A0E3B7F0096A291 /* NetworkClientFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkClientFactory.swift; sourceTree = ""; }; - FA7336BE2A0E3B7F0096A291 /* RequestConfiguratorFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestConfiguratorFactory.swift; sourceTree = ""; }; - FA7336BF2A0E3B7F0096A291 /* RequestSignerFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestSignerFactory.swift; sourceTree = ""; }; - FA7336C02A0E3B7F0096A291 /* ResponseDecodersFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseDecodersFactory.swift; sourceTree = ""; }; - FA7336C72A0E3B870096A291 /* RESTRequestConfigurator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTRequestConfigurator.swift; sourceTree = ""; }; - FA7336C82A0E3B870096A291 /* RequestConfigurator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestConfigurator.swift; sourceTree = ""; }; - FA7336C92A0E3B870096A291 /* NetworkWorker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkWorker.swift; sourceTree = ""; }; - FA7336CB2A0E3B870096A291 /* RequestConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestConfig.swift; sourceTree = ""; }; - FA7336CC2A0E3B870096A291 /* ResponseDecoderType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseDecoderType.swift; sourceTree = ""; }; - FA7336CD2A0E3B870096A291 /* RequestSigningType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestSigningType.swift; sourceTree = ""; }; - FA7336CE2A0E3B870096A291 /* NetworkRequestType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkRequestType.swift; sourceTree = ""; }; - FA7336CF2A0E3B870096A291 /* NetworkClientType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkClientType.swift; sourceTree = ""; }; - FA7336D12A0E3B870096A291 /* JSONResponseDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONResponseDecoder.swift; sourceTree = ""; }; - FA7336D22A0E3B870096A291 /* ResponseDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseDecoder.swift; sourceTree = ""; }; - FA7336D42A0E3B870096A291 /* AlchemyRequestSigner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlchemyRequestSigner.swift; sourceTree = ""; }; - FA7336D52A0E3B870096A291 /* RequestSigner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestSigner.swift; sourceTree = ""; }; - FA7336D72A0E3B870096A291 /* NetworkClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkClient.swift; sourceTree = ""; }; - FA7336D82A0E3B870096A291 /* RESTNetworkClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTNetworkClient.swift; sourceTree = ""; }; - FA7336E82A0E3CCC0096A291 /* HTTPMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; - FA7336EA2A0E3D0A0096A291 /* HTTPHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeader.swift; sourceTree = ""; }; - FA7336F72A0E3E1B0096A291 /* NetworkingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingError.swift; sourceTree = ""; }; FA7336FC2A132F740096A291 /* AlchemyHistoryOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlchemyHistoryOperationFactory.swift; sourceTree = ""; }; FA7337082A1339890096A291 /* AssetTransactionData+AlchemyHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AssetTransactionData+AlchemyHistory.swift"; sourceTree = ""; }; FA74359429C0733E0085A47E /* Array+Difference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Difference.swift"; sourceTree = ""; }; @@ -6121,6 +6101,11 @@ FAD429312A865695001D6A16 /* CheckboxButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxButton.swift; sourceTree = ""; }; FAD429332A8656B6001D6A16 /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; }; FAD429342A8656B7001D6A16 /* UICollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionView.swift; sourceTree = ""; }; + FAD5FF242C463C07003201F5 /* AccountStatisticsFetching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountStatisticsFetching.swift; sourceTree = ""; }; + FAD5FF262C463C4F003201F5 /* AccountStatistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountStatistics.swift; sourceTree = ""; }; + FAD5FF2A2C46464B003201F5 /* NomisAccountStatisticsFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NomisAccountStatisticsFetcher.swift; sourceTree = ""; }; + FAD5FF2D2C464717003201F5 /* NomisAccountStatisticsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NomisAccountStatisticsRequest.swift; sourceTree = ""; }; + FAD5FF302C4648DF003201F5 /* shared-features-spm */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "shared-features-spm"; path = "../shared-features-spm"; sourceTree = ""; }; FAD646C1284DD2CF007CCB92 /* StakingBalanceRelaychainStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingBalanceRelaychainStrategy.swift; sourceTree = ""; }; FAD646C3284DD2DA007CCB92 /* StakingBalanceRelaychainViewModelState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingBalanceRelaychainViewModelState.swift; sourceTree = ""; }; FAD646C5284DD2E5007CCB92 /* StakingBalanceRelaychainViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingBalanceRelaychainViewModelFactory.swift; sourceTree = ""; }; @@ -6264,6 +6249,7 @@ FC76E7D99A98423180BC572F /* LiquidityPoolsOverviewTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LiquidityPoolsOverviewTests.swift; sourceTree = ""; }; FD845193EDFC3A1D0BC73719 /* NftCollectionProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftCollectionProtocols.swift; sourceTree = ""; }; FD8B69E9E18C11EAEC9284B3 /* LiquidityPoolsOverviewViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LiquidityPoolsOverviewViewController.swift; sourceTree = ""; }; + FDD63BEB84A28855006BE680 /* AccountStatisticsRouter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountStatisticsRouter.swift; sourceTree = ""; }; FE4AF0849E32E5B9C72E2ABB /* RecommendedValidatorListViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RecommendedValidatorListViewFactory.swift; sourceTree = ""; }; FE826F356F6D72EACFB0AE31 /* NftSendConfirmPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftSendConfirmPresenter.swift; sourceTree = ""; }; FF4688AF0658F8BB7A90C2BE /* ExportMnemonicConfirmViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExportMnemonicConfirmViewFactory.swift; sourceTree = ""; }; @@ -6295,6 +6281,7 @@ FA8810C82BDCAF260084CC4B /* SSFTransferService in Frameworks */, FA72546B2AC2F12D00EC47A6 /* WalletConnectNetworking in Frameworks */, FA8810CA2BDCAF260084CC4B /* SSFUtils in Frameworks */, + FAF600752C48D79600E56558 /* Cosmos in Frameworks */, FA8FD1882AF4BEDD00354482 /* Swime in Frameworks */, FA8810BA2BDCAF260084CC4B /* SSFPools in Frameworks */, FA8810A42BDCAF260084CC4B /* SSFChainRegistry in Frameworks */, @@ -7335,6 +7322,14 @@ path = ExportMnemonicConfirm; sourceTree = ""; }; + 2CFE52D4FA09CD95EA70283E /* AccountStatistics */ = { + isa = PBXGroup; + children = ( + 26222D0DBE5CEF0BA7DCCEF7 /* AccountStatisticsTests.swift */, + ); + path = AccountStatistics; + sourceTree = ""; + }; 2E04CA9A3624EA1AD62722E8 /* WalletOption */ = { isa = PBXGroup; children = ( @@ -7765,6 +7760,7 @@ 749EAA035EECE4D63C56C358 /* LiquidityPoolSupplyConfirm */, 48EAF80DCC0C537917FC5A23 /* LiquidityPoolRemoveLiquidity */, BDB80385E6818AE7707DDFF8 /* LiquidityPoolRemoveLiquidityConfirm */, + 2CFE52D4FA09CD95EA70283E /* AccountStatistics */, ); path = Modules; sourceTree = ""; @@ -9153,6 +9149,7 @@ 8490139F24A80984008F705E = { isa = PBXGroup; children = ( + FAD5FF2F2C4648DF003201F5 /* Packages */, 849013AA24A80984008F705E /* fearless */, 849013C124A80986008F705E /* fearlessTests */, 8438E1D024BFAAD2001BDB13 /* fearlessIntegrationTests */, @@ -9322,6 +9319,7 @@ 45C94A390068322611CA7C02 /* SwapTransactionDetail */, DA46895F73B0FB1064146E47 /* AssetNetworks */, 0AEF32A92BEEDB9B5A60A433 /* ClaimCrowdloanRewards */, + 8E7E74939224B5DA444D4AFA /* AccountStatistics */, ); path = Modules; sourceTree = ""; @@ -9479,6 +9477,7 @@ FAC6CD992BA809160013A17E /* ReceiveInfo.swift */, FAC6CDA02BA80CB10013A17E /* WalletTransactionType.swift */, FAC6CDAA2BA819540013A17E /* SearchData.swift */, + FAD5FF262C463C4F003201F5 /* AccountStatistics.swift */, ); path = Model; sourceTree = ""; @@ -10916,6 +10915,20 @@ path = StakingRewardPayouts; sourceTree = ""; }; + 8E7E74939224B5DA444D4AFA /* AccountStatistics */ = { + isa = PBXGroup; + children = ( + 4BF646F59913E95891915BDC /* AccountStatisticsProtocols.swift */, + FDD63BEB84A28855006BE680 /* AccountStatisticsRouter.swift */, + BB86E65E22C0AF7EDD0701A4 /* AccountStatisticsPresenter.swift */, + 0542BF70B1BADBF1459D57FB /* AccountStatisticsInteractor.swift */, + 7C85B1F841C281165D7AACB1 /* AccountStatisticsViewController.swift */, + 7743EA304BC53649D0473225 /* AccountStatisticsViewLayout.swift */, + 62CD1B83902C1B5763476EFF /* AccountStatisticsAssembly.swift */, + ); + path = AccountStatistics; + sourceTree = ""; + }; 9439C16432098735E8F4C122 /* LiquidityPoolDetails */ = { isa = PBXGroup; children = ( @@ -13115,17 +13128,6 @@ path = ResponseDecoders; sourceTree = ""; }; - FA3067392B6257F6006A0BA5 /* Network */ = { - isa = PBXGroup; - children = ( - FA7336BD2A0E3B7F0096A291 /* NetworkClientFactory.swift */, - FA7336BE2A0E3B7F0096A291 /* RequestConfiguratorFactory.swift */, - FA7336BF2A0E3B7F0096A291 /* RequestSignerFactory.swift */, - FA7336C02A0E3B7F0096A291 /* ResponseDecodersFactory.swift */, - ); - path = Network; - sourceTree = ""; - }; FA30673A2B625806006A0BA5 /* Storage */ = { isa = PBXGroup; children = ( @@ -13451,6 +13453,7 @@ FA38C9A32760700B005C5577 /* Services */ = { isa = PBXGroup; children = ( + FAD5FF232C463BEE003201F5 /* AccountStatistics */, FA09AD332C37AB9400BE0B9C /* TransactionObserver */, FA34EE902B98711F0042E73E /* CrowdloanService.swift */, FA34EE912B9871200042E73E /* Onboarding */, @@ -13513,6 +13516,14 @@ path = Parachain; sourceTree = ""; }; + FA4B098C2C4674C9001B73F9 /* Request */ = { + isa = PBXGroup; + children = ( + FAD5FF2D2C464717003201F5 /* NomisAccountStatisticsRequest.swift */, + ); + path = Request; + sourceTree = ""; + }; FA4B929C2844D0C80003BCEF /* SyntaxSugar */ = { isa = PBXGroup; children = ( @@ -13873,83 +13884,10 @@ isa = PBXGroup; children = ( FA30673A2B625806006A0BA5 /* Storage */, - FA3067392B6257F6006A0BA5 /* Network */, ); path = ComponentFactories; sourceTree = ""; }; - FA7336C52A0E3B870096A291 /* Network */ = { - isa = PBXGroup; - children = ( - FA7336C92A0E3B870096A291 /* NetworkWorker.swift */, - FA7336E72A0E3CBC0096A291 /* Models */, - FA7336C62A0E3B870096A291 /* RequestConfigurators */, - FA7336CA2A0E3B870096A291 /* Config */, - FA7336D02A0E3B870096A291 /* ResponseDecoders */, - FA7336D32A0E3B870096A291 /* RequestSigners */, - FA7336D62A0E3B870096A291 /* NetworkClient */, - ); - path = Network; - sourceTree = ""; - }; - FA7336C62A0E3B870096A291 /* RequestConfigurators */ = { - isa = PBXGroup; - children = ( - FA7336C72A0E3B870096A291 /* RESTRequestConfigurator.swift */, - FA7336C82A0E3B870096A291 /* RequestConfigurator.swift */, - ); - path = RequestConfigurators; - sourceTree = ""; - }; - FA7336CA2A0E3B870096A291 /* Config */ = { - isa = PBXGroup; - children = ( - FA7336CB2A0E3B870096A291 /* RequestConfig.swift */, - FA7336CC2A0E3B870096A291 /* ResponseDecoderType.swift */, - FA7336CD2A0E3B870096A291 /* RequestSigningType.swift */, - FA7336CE2A0E3B870096A291 /* NetworkRequestType.swift */, - FA7336CF2A0E3B870096A291 /* NetworkClientType.swift */, - ); - path = Config; - sourceTree = ""; - }; - FA7336D02A0E3B870096A291 /* ResponseDecoders */ = { - isa = PBXGroup; - children = ( - FA7336D12A0E3B870096A291 /* JSONResponseDecoder.swift */, - FA7336D22A0E3B870096A291 /* ResponseDecoder.swift */, - ); - path = ResponseDecoders; - sourceTree = ""; - }; - FA7336D32A0E3B870096A291 /* RequestSigners */ = { - isa = PBXGroup; - children = ( - FA7336D42A0E3B870096A291 /* AlchemyRequestSigner.swift */, - FA7336D52A0E3B870096A291 /* RequestSigner.swift */, - ); - path = RequestSigners; - sourceTree = ""; - }; - FA7336D62A0E3B870096A291 /* NetworkClient */ = { - isa = PBXGroup; - children = ( - FA7336D72A0E3B870096A291 /* NetworkClient.swift */, - FA7336D82A0E3B870096A291 /* RESTNetworkClient.swift */, - ); - path = NetworkClient; - sourceTree = ""; - }; - FA7336E72A0E3CBC0096A291 /* Models */ = { - isa = PBXGroup; - children = ( - FA7336E82A0E3CCC0096A291 /* HTTPMethod.swift */, - FA7336EA2A0E3D0A0096A291 /* HTTPHeader.swift */, - FA7336F72A0E3E1B0096A291 /* NetworkingError.swift */, - ); - path = Models; - sourceTree = ""; - }; FA7741D32B6A350200358315 /* StakingRewards */ = { isa = PBXGroup; children = ( @@ -14793,7 +14731,6 @@ children = ( FA3067292B6246BD006A0BA5 /* Storage */, FAFB47D52ABD588D0008F8CA /* Repository */, - FA7336C52A0E3B870096A291 /* Network */, ); path = CoreComponents; sourceTree = ""; @@ -15342,6 +15279,33 @@ path = BackupWalletImported; sourceTree = ""; }; + FAD5FF232C463BEE003201F5 /* AccountStatistics */ = { + isa = PBXGroup; + children = ( + FAD5FF2C2C464705003201F5 /* Nomis */, + FAD5FF242C463C07003201F5 /* AccountStatisticsFetching.swift */, + ); + path = AccountStatistics; + sourceTree = ""; + }; + FAD5FF2C2C464705003201F5 /* Nomis */ = { + isa = PBXGroup; + children = ( + FA4B098C2C4674C9001B73F9 /* Request */, + FAD5FF2A2C46464B003201F5 /* NomisAccountStatisticsFetcher.swift */, + FA4B098D2C47804F001B73F9 /* NomisRequestSigner.swift */, + ); + path = Nomis; + sourceTree = ""; + }; + FAD5FF2F2C4648DF003201F5 /* Packages */ = { + isa = PBXGroup; + children = ( + FAD5FF302C4648DF003201F5 /* shared-features-spm */, + ); + name = Packages; + sourceTree = ""; + }; FAD646BF284DD2B2007CCB92 /* Flow */ = { isa = PBXGroup; children = ( @@ -15852,6 +15816,7 @@ FA8810CB2BDCAF260084CC4B /* SSFXCM */, FA8810CD2BDCAF260084CC4B /* SoraKeystore */, FA8810CF2BDCAF260084CC4B /* keccak */, + FAF600742C48D79600E56558 /* Cosmos */, ); productName = fearless; productReference = 849013A824A80984008F705E /* fearless.app */; @@ -15923,6 +15888,7 @@ FA8FD17F2AF4B55100354482 /* XCRemoteSwiftPackageReference "Web3.swift" */, FA8FD1862AF4BEDD00354482 /* XCRemoteSwiftPackageReference "Swime" */, FA8810962BDCAF260084CC4B /* XCRemoteSwiftPackageReference "shared-features-spm" */, + FAF600732C48D79500E56558 /* XCRemoteSwiftPackageReference "Cosmos" */, ); productRefGroup = 849013A924A80984008F705E /* Products */; projectDirPath = ""; @@ -16362,7 +16328,6 @@ FA17B4B527E858CB006E0735 /* JsonLoadingFailedAlertConfig.swift in Sources */, FAD9AAC52B8DFF6700AA603B /* PrefixStorageRequestWorker.swift in Sources */, F477CD26262EAD30004DF739 /* StakingPayoutViewModelFactory.swift in Sources */, - FA7336E12A0E3B880096A291 /* JSONResponseDecoder.swift in Sources */, 844E1E19266142080076AC59 /* BondedState+Status.swift in Sources */, 8493D0E126FF4F5000A28008 /* ScaleCoder+Extension.swift in Sources */, 8428766724ADF22000D91AD8 /* TransformAnimator+Common.swift in Sources */, @@ -16504,7 +16469,6 @@ FA34EE982B9871200042E73E /* OnboardingConfig.swift in Sources */, 84CFF1EB26526FBC00DB7CF7 /* StakingBondMoreConfirmViewModel.swift in Sources */, FAA0137728DA12E3000A5230 /* StakingRedeemConfirmationPoolViewModelFactory.swift in Sources */, - FA7336E02A0E3B880096A291 /* NetworkClientType.swift in Sources */, 8490142D24A935FE008F705E /* WebPresentable.swift in Sources */, 842D1E7B24D0667E00C30A7A /* TriangularedButton.swift in Sources */, FA38C99D275F3B1B005C5577 /* ChainOptionsViewModelFactory.swift in Sources */, @@ -16547,7 +16511,6 @@ FACD42B72A5BE8F3009975AA /* PolkaswapAdjustmentViewLoadingCollector.swift in Sources */, FA30D4EF28BF32EA00548C1E /* SortPickerTableViewCell.swift in Sources */, 84563CFF24EFE07E0055591D /* Storage+Identifiable.swift in Sources */, - FA7336E32A0E3B880096A291 /* AlchemyRequestSigner.swift in Sources */, FA80391728DD70D7007365E8 /* AccountOperationFactory.swift in Sources */, FA2569BF274CE74100875A53 /* NetworkFeeView+Protocol.swift in Sources */, 843C49E124DFFC9500B71DDA /* AccountImportSource+ViewModel.swift in Sources */, @@ -16740,7 +16703,6 @@ 849013DE24A927E2008F705E /* LocalizationManager+Shared.swift in Sources */, FAC6CD862BA7F9990013A17E /* Pagination.swift in Sources */, FA904D902B04958500DAEC2D /* SortTableCell.swift in Sources */, - FA7336DC2A0E3B880096A291 /* RequestConfig.swift in Sources */, 849013DC24A927E2008F705E /* ApplicationConfigs.swift in Sources */, 84ACEBFA261E684A00AAE665 /* WalletHistoryFilter.swift in Sources */, FA37AE432859E1E4001DCA96 /* ScheduleRevokeDelegationCall.swift in Sources */, @@ -16877,7 +16839,6 @@ FAD646D2284F1069007CCB92 /* StakingBondMoreConfirmationRelaychainViewModelState.swift in Sources */, 849014C224AA87E4008F705E /* LocalAuthPresenter.swift in Sources */, 84CA68D126BE99ED003B9453 /* RuntimeProviderFactory.swift in Sources */, - FA7336E22A0E3B880096A291 /* ResponseDecoder.swift in Sources */, 84100F3626A6069200A5054E /* IconTitleValueView.swift in Sources */, 842876AB24AE049B00D91AD8 /* SelectionItemViewProtocol.swift in Sources */, FA09AD372C37AF9200BE0B9C /* SubstrateTransactionObserver.swift in Sources */, @@ -16998,6 +16959,7 @@ FAD4290D2A86567F001D6A16 /* BannersViewModelFactory.swift in Sources */, FACD429B2A5BE811009975AA /* SubstrateStorageMigrator.swift in Sources */, F4A6C9E4265283EB00FABF98 /* StakingStateViewModelFactory+Alerts.swift in Sources */, + FAD5FF2E2C464717003201F5 /* NomisAccountStatisticsRequest.swift in Sources */, FAD429202A865680001D6A16 /* WalletNameProtocols.swift in Sources */, FA7741DE2B6A350200358315 /* StakingRewardFetcher.swift in Sources */, 84452F5825D5C30600F47EC5 /* FilesRepository.swift in Sources */, @@ -17024,12 +16986,10 @@ 84A2C90C24E192F50020D3B7 /* ShakeAnimator.swift in Sources */, FA6262352AC2E35A005D3D95 /* WalletConnectConfirmationInputData.swift in Sources */, 84452F2B25D5B84200F47EC5 /* RuntimeRegistryServiceProtocol.swift in Sources */, - FA7336DF2A0E3B880096A291 /* NetworkRequestType.swift in Sources */, FACD42992A5BE811009975AA /* SubstrateStorageMigrator+Sync.swift in Sources */, FAA0132E28DA12B6000A5230 /* StakingPoolNetworkInfoViewModel.swift in Sources */, FAADC1AA29261F7000DA9903 /* PoolRolesConfirmInteractor.swift in Sources */, 84F4A9A42551A8F3000CF0A3 /* AccountExportPasswordError.swift in Sources */, - FA7336DE2A0E3B880096A291 /* RequestSigningType.swift in Sources */, FAF9C2AE2AAF3FDF00A61D21 /* GetPreinstalledWalletAssembly.swift in Sources */, 84CA68DB26BEA33F003B9453 /* ChainRegistryFactory.swift in Sources */, FAD428982A860C9B001D6A16 /* EthereumNodeFetching.swift in Sources */, @@ -17095,6 +17055,7 @@ 84031C17263EC95C008FD9D4 /* SetPayeeCall.swift in Sources */, 84100F3A26A60B0900A5054E /* YourValidatorListStatusSectionView.swift in Sources */, 84F30EEC25FFF40C00039D09 /* Nomination.swift in Sources */, + FAD5FF272C463C4F003201F5 /* AccountStatistics.swift in Sources */, FA7254442AC2E48500EC47A6 /* WalletConnectSocketFactory.swift in Sources */, FA37AE3C2859CCD8001DCA96 /* StakingUnbondConfirmParachainViewModelFactory.swift in Sources */, FA38C9C32761E77A005C5577 /* AccountViewModelFactory.swift in Sources */, @@ -17189,7 +17150,6 @@ FA34EE8F2B98710C0042E73E /* NoneStateOptional.swift in Sources */, AE9EF278260B2E160026910A /* StoriesFactory.swift in Sources */, FA67200D2914B163007EB694 /* StorageKeyExtractor.swift in Sources */, - FA7336E52A0E3B880096A291 /* NetworkClient.swift in Sources */, C65A6592288E5CF400679D65 /* AccountInfoFetching.swift in Sources */, FA256A20274CE7D600875A53 /* AstarBonusService.swift in Sources */, FAD067BD2C2044B10050291F /* AssetManagementViewModel.swift in Sources */, @@ -17271,7 +17231,6 @@ FA99423528053C5000D771E5 /* ChainAccountInfo.swift in Sources */, AEA0C8BE2681141700F9666F /* YourValidatorList+SelectedList.swift in Sources */, FA37AE4D28603C37001DCA96 /* StakingUpdatedEvent.swift in Sources */, - FA7336DD2A0E3B880096A291 /* ResponseDecoderType.swift in Sources */, FA37AE3F2859DF94001DCA96 /* StakingRedeemParachainStrategy.swift in Sources */, 8430AAE926022F69005B1066 /* StashState.swift in Sources */, 8428768424AE046300D91AD8 /* LanguageSelectionInteractor.swift in Sources */, @@ -17305,7 +17264,6 @@ FA3430F0285065AD002B5975 /* StakingUnbondConfirmRelaychainViewModelState.swift in Sources */, FA99422D28002BB800D771E5 /* MissingAccountOptions.swift in Sources */, 8430AB1726023D2D005B1066 /* BaseStashNextState.swift in Sources */, - FA7336C42A0E3B7F0096A291 /* ResponseDecodersFactory.swift in Sources */, FA34EEE92B98723C0042E73E /* BalanceLocksDetailAssembly.swift in Sources */, 841AAC2126F6860B00F0A25E /* AssetBalanceFormatterFactory.swift in Sources */, F47BBD512630B7D80087DA11 /* StakingBalancePresenter.swift in Sources */, @@ -17422,7 +17380,6 @@ 84FACCD925F8C22A00F32ED4 /* BigInt+Hex.swift in Sources */, F409672626B29B04008CD244 /* UIScrollView+ScrollToPage.swift in Sources */, 07D05E6628EF0BE500B66C70 /* SelectValidatorsConfirmPoolStrategy.swift in Sources */, - FA7336C32A0E3B7F0096A291 /* RequestSignerFactory.swift in Sources */, FA7C9A6C2BA004E80031580A /* ArrowsquidHistoryOperationFactory.swift in Sources */, 846A2C4525271F0100731018 /* DateFormatter.swift in Sources */, F4F65C3326D8B81A002EE838 /* FWChartViewProtocol.swift in Sources */, @@ -17455,7 +17412,6 @@ AEB9979026119E4D005C60A5 /* StoriesViewModelFactory.swift in Sources */, 84754C852510A1A400854599 /* ModalAlertPresenting.swift in Sources */, 8494424A265306BD0016E7BD /* ChangeRewardDestinationViewModel.swift in Sources */, - FA7336F82A0E3E1B0096A291 /* NetworkingError.swift in Sources */, 845B821526EF657700D25C72 /* PersistentValueSettings.swift in Sources */, F4DCAE4726207EF900CCA6BF /* PayoutRewardsServiceProtocol.swift in Sources */, FAA0139128DA1303000A5230 /* StakingPayoutConfirmationPoolStrategy.swift in Sources */, @@ -17521,7 +17477,6 @@ FA80391528DC2DA2007365E8 /* StakingPoolPalletID.swift in Sources */, 843910B4253EE52100E3C217 /* WalletBalanceChanged.swift in Sources */, 07B6BC7C28B875D800621864 /* UIControl.swift in Sources */, - FA7336E92A0E3CCC0096A291 /* HTTPMethod.swift in Sources */, FAD067C22C2044B10050291F /* AssetManagementPresenter.swift in Sources */, FA93A2FD2834AF930021330F /* ValidatorInfoParachainViewModelFactory.swift in Sources */, 84FAB0692542EBDE00319F74 /* SearchData+Contacts.swift in Sources */, @@ -17540,7 +17495,6 @@ 844DB61F262D9C070025A8F0 /* ChainHistoryRange.swift in Sources */, FA34EED82B98723C0042E73E /* OnboardingStartAssembly.swift in Sources */, FA2569C3274CE74100875A53 /* BottomSheetInfoTableCell.swift in Sources */, - FA7336C22A0E3B7F0096A291 /* RequestConfiguratorFactory.swift in Sources */, FA004897282CCECA0032FF49 /* SelectValidatorsStartRelaychainViewModelState.swift in Sources */, 07BF3D9B2B3D8C370046ABF4 /* ManualOperation.swift in Sources */, 8490142C24A935FE008F705E /* ErrorPresentable.swift in Sources */, @@ -17733,7 +17687,6 @@ FA2FC80128B3807C00CC0A42 /* StakingPoolJoinChoosePoolInteractor.swift in Sources */, FA34EED42B98723C0042E73E /* OnboardingStartViewLayout.swift in Sources */, FAA0139728DA1312000A5230 /* StakingBondMoreConfirmationPoolViewModelFactory.swift in Sources */, - FA7336E42A0E3B880096A291 /* RequestSigner.swift in Sources */, 84D1111126B932480016D962 /* AssetModel.swift in Sources */, C64ECCE328873F2500CFF434 /* ChainAssetsFetching.swift in Sources */, 848FFE8325E686C200652AA5 /* StorageDecodingOperation.swift in Sources */, @@ -17770,6 +17723,7 @@ FAD4292E2A865680001D6A16 /* BackupWalletImportedProtocols.swift in Sources */, 84BAB6102642C286007782D0 /* SelectedRebondVariant.swift in Sources */, 84F5107C263C0C11005D15AE /* AnyProviderCleaning.swift in Sources */, + FAD5FF2B2C46464B003201F5 /* NomisAccountStatisticsFetcher.swift in Sources */, FAA0134628DA12CD000A5230 /* StakingUnbondSetupPoolViewModelState.swift in Sources */, 84BEE22325646AC000D05EB3 /* SelectedUsernameChanged.swift in Sources */, 8467FD4124ED3C72005D486C /* AlignableContentControl.swift in Sources */, @@ -17789,7 +17743,6 @@ FAA013A728DA133E000A5230 /* ShadowRoundedBackground.swift in Sources */, AE9EF26E260A82D50026910A /* SlideViewModel.swift in Sources */, 84754C9A2513871300854599 /* SNAddressType+Codable.swift in Sources */, - FA7336DB2A0E3B880096A291 /* NetworkWorker.swift in Sources */, F78EA110179B6D75DDF53F8B /* AccountCreateWireframe.swift in Sources */, 84DEAA7B265DB987000DDADE /* CrowdloanContributeCall.swift in Sources */, 84963D6E26F91826003FE8E4 /* RemoteSubscriptionRequests.swift in Sources */, @@ -17877,7 +17830,6 @@ FA38C98E275DFB8E005C5577 /* BaseTopBar.swift in Sources */, FAB707652BB3C06900A1131C /* AssetsAccountRequest.swift in Sources */, AE6DE7322627EA930018D5B5 /* PayoutCall.swift in Sources */, - FA7336D92A0E3B880096A291 /* RESTRequestConfigurator.swift in Sources */, FAD0068127EA252400C97E09 /* AboutViewState.swift in Sources */, 84DD5F26263D72C400425ACF /* ExtrinsicFeeProxy.swift in Sources */, C633AD2B29C1955000EA8C29 /* ExtendedTouchAreaButton.swift in Sources */, @@ -18035,6 +17987,7 @@ C665E4F029C35801001946D1 /* TimeFormatter.swift in Sources */, 073417B3298BA28300104F41 /* EquilibriumTotalBalanceServiceFactory.swift in Sources */, 84FAB0632542C8D600319F74 /* ContactItem.swift in Sources */, + FAD5FF252C463C07003201F5 /* AccountStatisticsFetching.swift in Sources */, C67E781927B3AC350053346B /* CheckPincodeViewController.swift in Sources */, FA5137B029AC6F2F00560EBA /* PolkaswapDisclaimerProtocols.swift in Sources */, FACD42972A5BE811009975AA /* SettingsMigrator.swift in Sources */, @@ -18399,7 +18352,6 @@ FAA0133F28DA12B6000A5230 /* StakingPoolManagementInteractor.swift in Sources */, FA8F63AB29825C90004B8CD4 /* AccountShareFactory.swift in Sources */, 84F6B6502619E1ED0038F10D /* Int+Operations.swift in Sources */, - FA7336DA2A0E3B880096A291 /* RequestConfigurator.swift in Sources */, FA286B182A3043DB008BD527 /* CrossChainInteractor.swift in Sources */, FA6C175429935DAE00A55254 /* AssetTransactionData+SubqueryHistory.swift in Sources */, C6584E352982524700592A92 /* WalletTransactionHistoryDependencyContainer.swift in Sources */, @@ -18565,6 +18517,7 @@ 07DE95C828A169A600E9C2CB /* AssetListSearchPresenter.swift in Sources */, FAA0136C28DA12E3000A5230 /* StakingRedeemConfirmationInteractor.swift in Sources */, 8401AEC52642A71D000B03E3 /* StakingRebondConfirmationViewFactory.swift in Sources */, + FA4B098E2C47804F001B73F9 /* NomisRequestSigner.swift in Sources */, 07D05E4B28EEFF3100B66C70 /* SelectValidatorsStartPoolViewModelFactory.swift in Sources */, 27FA1D57A06AA3A030D226B6 /* StakingUnbondConfirmWireframe.swift in Sources */, 843A2C7326A8641400266F53 /* MultiValueView.swift in Sources */, @@ -18828,7 +18781,6 @@ 07F817F72AD4051A00B87358 /* Interaction.swift in Sources */, FEB21960BEBE9863BEC63F50 /* FiltersViewController.swift in Sources */, 3EC834936AEA6088FBC926B4 /* FiltersViewLayout.swift in Sources */, - FA7336E62A0E3B880096A291 /* RESTNetworkClient.swift in Sources */, 719B429B58B9A0551381F92F /* FiltersViewFactory.swift in Sources */, FAAA29362B8DCE930089AFE6 /* StorageRequest.swift in Sources */, FA4CC666281801CB00A7E85F /* StakingUnitInfoView.swift in Sources */, @@ -18868,7 +18820,6 @@ 0C4F3F7AB14F4851C12974B6 /* WarningAlertProtocols.swift in Sources */, FAD429082A86567F001D6A16 /* BannersPresenter.swift in Sources */, 2B99F241DC91645B1226E10C /* WarningAlertWireframe.swift in Sources */, - FA7336C12A0E3B7F0096A291 /* NetworkClientFactory.swift in Sources */, 0BD66304BF73EBDFE1857380 /* WarningAlertPresenter.swift in Sources */, 5712A48A0C8AEFD9355FD9DA /* WarningAlertInteractor.swift in Sources */, FA5085AC2C33C6D4002DF97D /* SafeArray.swift in Sources */, @@ -19005,7 +18956,6 @@ AB678EAA622BFEAEEA8166F2 /* AllDoneViewLayout.swift in Sources */, 073B34BF2AE91FE600DC5106 /* WalletConnectProposalDataValidating.swift in Sources */, 37AE170856990F9FBEF052FC /* AllDoneAssembly.swift in Sources */, - FA7336EB2A0E3D0A0096A291 /* HTTPHeader.swift in Sources */, E2645EB7614F4C7A60B48777 /* PolkaswapAdjustmentProtocols.swift in Sources */, 27CD06DA03B268E2C6A90B15 /* PolkaswapAdjustmentRouter.swift in Sources */, 59838EECC194BB0E6E0AEAA2 /* PolkaswapAdjustmentPresenter.swift in Sources */, @@ -19131,6 +19081,13 @@ 8A957CAF82C856E61054B02F /* LiquidityPoolRemoveLiquidityConfirmViewController.swift in Sources */, 10DEF797CB3DC5BF0903EC4C /* LiquidityPoolRemoveLiquidityConfirmViewLayout.swift in Sources */, E667BD4B6BA45B9B3464AD85 /* LiquidityPoolRemoveLiquidityConfirmAssembly.swift in Sources */, + 00E9DD69FCE94A4F4929B419 /* AccountStatisticsProtocols.swift in Sources */, + 887CE12C7C59F5DB092E9227 /* AccountStatisticsRouter.swift in Sources */, + 11FDC274784B05B690368C07 /* AccountStatisticsPresenter.swift in Sources */, + 5E974C26655D3E64AD6A923D /* AccountStatisticsInteractor.swift in Sources */, + 2BBE065C2A5C31B830DE0957 /* AccountStatisticsViewController.swift in Sources */, + 52AEA30073F8CB856B692757 /* AccountStatisticsViewLayout.swift in Sources */, + 8FC70700D2154F472636D458 /* AccountStatisticsAssembly.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -19307,6 +19264,7 @@ B40863AA7377BFE5F8A30259 /* LiquidityPoolSupplyConfirmTests.swift in Sources */, 6BF307ADE63FA92389340779 /* LiquidityPoolRemoveLiquidityTests.swift in Sources */, ECA54AB8148BBA63084353FD /* LiquidityPoolRemoveLiquidityConfirmTests.swift in Sources */, + 8852522BE02B6244A00E85A1 /* AccountStatisticsTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -19877,6 +19835,14 @@ minimumVersion = 3.0.0; }; }; + FAF600732C48D79500E56558 /* XCRemoteSwiftPackageReference "Cosmos" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/evgenyneu/Cosmos.git"; + requirement = { + kind = exactVersion; + version = 25.0.1; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -20070,6 +20036,11 @@ package = FA8FD1862AF4BEDD00354482 /* XCRemoteSwiftPackageReference "Swime" */; productName = Swime; }; + FAF600742C48D79600E56558 /* Cosmos */ = { + isa = XCSwiftPackageProductDependency; + package = FAF600732C48D79500E56558 /* XCRemoteSwiftPackageReference "Cosmos" */; + productName = Cosmos; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved b/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4ac8d64f6..d8c29c5e8 100644 --- a/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,15 @@ "version" : "5.3.0" } }, + { + "identity" : "cosmos", + "kind" : "remoteSourceControl", + "location" : "https://github.com/evgenyneu/Cosmos.git", + "state" : { + "revision" : "40ba10aaf175bf50abefd0e518bd3b40862af3b1", + "version" : "25.0.1" + } + }, { "identity" : "cryptoswift", "kind" : "remoteSourceControl", @@ -126,15 +135,6 @@ "version" : "0.1.7" } }, - { - "identity" : "shared-features-spm", - "kind" : "remoteSourceControl", - "location" : "https://github.com/soramitsu/shared-features-spm.git", - "state" : { - "branch" : "fearless-wallet", - "revision" : "bb864277bcf22a6bd02d5abd7c8f07c5aa37236b" - } - }, { "identity" : "swift-atomics", "kind" : "remoteSourceControl", @@ -225,6 +225,15 @@ "version" : "1.3.1" } }, + { + "identity" : "swiftformat", + "kind" : "remoteSourceControl", + "location" : "https://github.com/nicklockwood/SwiftFormat", + "state" : { + "revision" : "e4965d9e8acebb8341a6ebd20b910c882157482d", + "version" : "0.54.1" + } + }, { "identity" : "swiftimagereadwrite", "kind" : "remoteSourceControl", diff --git a/fearless/ApplicationLayer/Alchemy/AlchemyRequest.swift b/fearless/ApplicationLayer/Alchemy/AlchemyRequest.swift index d7e6ed329..deb6af075 100644 --- a/fearless/ApplicationLayer/Alchemy/AlchemyRequest.swift +++ b/fearless/ApplicationLayer/Alchemy/AlchemyRequest.swift @@ -1,5 +1,6 @@ import Foundation import FearlessKeys +import SSFNetwork final class AlchemyRequest: RequestConfig { private enum Constants { diff --git a/fearless/ApplicationLayer/Alchemy/AlchemyService.swift b/fearless/ApplicationLayer/Alchemy/AlchemyService.swift index 2063f2c42..62b45a98c 100644 --- a/fearless/ApplicationLayer/Alchemy/AlchemyService.swift +++ b/fearless/ApplicationLayer/Alchemy/AlchemyService.swift @@ -1,6 +1,7 @@ import Foundation import SSFUtils import BigInt +import SSFNetwork struct EthereumBalanceRequestParams: Encodable { let address: String @@ -18,7 +19,7 @@ final class AlchemyService { let body = JSONRPCInfo(identifier: 1, jsonrpc: "2.0", method: AlchemyEndpoint.getAssetTransfers.rawValue, params: [request]) let paramsEncoded = try JSONEncoder().encode(body) let request = AlchemyRequest(body: paramsEncoded) - let worker = NetworkWorker() + let worker = NetworkWorkerImpl() let response: AlchemyResponse = try await worker.performRequest(with: request) return response } diff --git a/fearless/ApplicationLayer/Pricing/SoraSubqueryPriceFetcher.swift b/fearless/ApplicationLayer/Pricing/SoraSubqueryPriceFetcher.swift index 6769f3f64..cb7d2e02d 100644 --- a/fearless/ApplicationLayer/Pricing/SoraSubqueryPriceFetcher.swift +++ b/fearless/ApplicationLayer/Pricing/SoraSubqueryPriceFetcher.swift @@ -1,6 +1,7 @@ import Foundation import SSFModels import RobinHood +import SSFNetwork final class SoraSubqueryPriceFetcherDefault: SoraSubqueryPriceFetcher { func fetchPriceOperation( @@ -66,7 +67,7 @@ final class SoraSubqueryPriceFetcherDefault: SoraSubqueryPriceFetcher { baseURL: url, query: queryString(priceIds: priceIds, cursor: cursor) ) - let worker = NetworkWorker() + let worker = NetworkWorkerImpl() let response: GraphQLResponse = try await worker.performRequest(with: request) switch response { diff --git a/fearless/ApplicationLayer/Services/AccountStatistics/AccountStatisticsFetching.swift b/fearless/ApplicationLayer/Services/AccountStatistics/AccountStatisticsFetching.swift new file mode 100644 index 000000000..61b0b620d --- /dev/null +++ b/fearless/ApplicationLayer/Services/AccountStatistics/AccountStatisticsFetching.swift @@ -0,0 +1,9 @@ +import Foundation +import SSFNetwork + +protocol AccountStatisticsFetching { + func subscribeForStatistics( + address: String, + cacheOptions: CachedNetworkRequestTrigger + ) async throws -> AsyncThrowingStream, Error> +} diff --git a/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift new file mode 100644 index 000000000..030916945 --- /dev/null +++ b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift @@ -0,0 +1,39 @@ +import Foundation +import SSFNetwork + +enum NomisAccountStatisticsFetcherError: Error { + case badBaseURL +} + +final class NomisAccountStatisticsFetcher { + private let networkWorker: NetworkWorker + private let signer: RequestSigner + + init( + networkWorker: NetworkWorker, + signer: RequestSigner + ) { + self.networkWorker = networkWorker + self.signer = signer + } +} + +extension NomisAccountStatisticsFetcher: AccountStatisticsFetching { + func subscribeForStatistics( + address: String, + cacheOptions: CachedNetworkRequestTrigger + ) async throws -> AsyncThrowingStream, Error> { + guard let baseURL = URL(string: "https://api.nomis.cc/api/v1/multichain-score/wallet/") else { + throw NomisAccountStatisticsFetcherError.badBaseURL + } + + let request = try NomisAccountStatisticsRequest( + baseURL: baseURL, + address: address, + endpoint: "score" + ) + request.signingType = .custom(signer: signer) + + return await networkWorker.performRequest(with: request, withCacheOptions: cacheOptions) + } +} diff --git a/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisRequestSigner.swift b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisRequestSigner.swift new file mode 100644 index 000000000..ae9842faf --- /dev/null +++ b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisRequestSigner.swift @@ -0,0 +1,13 @@ +import Foundation +import SSFNetwork +import FearlessKeys + +final class NomisRequestSigner: RequestSigner { + func sign(request: inout URLRequest, config _: SSFNetwork.RequestConfig) throws { + let clientId = NomisApiKeys.nomisClientId + let apiKey = NomisApiKeys.nomisApiKey + + request.setValue(clientId, forHTTPHeaderField: "X-ClientId") + request.setValue(apiKey, forHTTPHeaderField: "X-API-Key") + } +} diff --git a/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/Request/NomisAccountStatisticsRequest.swift b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/Request/NomisAccountStatisticsRequest.swift new file mode 100644 index 000000000..ca43065f3 --- /dev/null +++ b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/Request/NomisAccountStatisticsRequest.swift @@ -0,0 +1,34 @@ +import Foundation +import SSFNetwork +import RobinHood +import SSFModels + +public class NomisAccountStatisticsRequest: RequestConfig { + private let address: String + + public init( + baseURL: URL, + address: String, + endpoint: String + ) throws { + self.address = address + + let finalEndpoint = [address, endpoint].joined(separator: "/") + + super.init( + baseURL: baseURL, + method: .get, + endpoint: finalEndpoint, + headers: nil, + body: nil + ) + } + + override public var cacheKey: String { + if let endpoint = endpoint { + return endpoint + address + } + + return address + } +} diff --git a/fearless/ApplicationLayer/Services/Onboarding/OnboardingService.swift b/fearless/ApplicationLayer/Services/Onboarding/OnboardingService.swift index 089a7472c..8349bfa52 100644 --- a/fearless/ApplicationLayer/Services/Onboarding/OnboardingService.swift +++ b/fearless/ApplicationLayer/Services/Onboarding/OnboardingService.swift @@ -37,7 +37,7 @@ extension OnboardingService: OnboardingServiceProtocol { body: nil, timeout: 5 ) - let worker = NetworkWorker() + let worker = NetworkWorkerImpl() return try await worker.performRequest(with: request) } } diff --git a/fearless/ApplicationLayer/StakingRewards/GiantsquidStakingRewardsFetcher.swift b/fearless/ApplicationLayer/StakingRewards/GiantsquidStakingRewardsFetcher.swift index 89d3800c1..bf59297e1 100644 --- a/fearless/ApplicationLayer/StakingRewards/GiantsquidStakingRewardsFetcher.swift +++ b/fearless/ApplicationLayer/StakingRewards/GiantsquidStakingRewardsFetcher.swift @@ -1,6 +1,7 @@ import Foundation import SoraFoundation import SSFModels +import SSFNetwork final class GiantsquidStakingRewardsFetcher { private let chain: ChainModel @@ -69,7 +70,7 @@ extension GiantsquidStakingRewardsFetcher: StakingRewardsFetcher { baseURL: blockExplorer.url, query: queryString ) - let worker = NetworkWorker() + let worker = NetworkWorkerImpl() let response: GraphQLResponse = try await worker.performRequest(with: request) switch response { diff --git a/fearless/ApplicationLayer/StakingRewards/ReefStakingRewardsFetcher.swift b/fearless/ApplicationLayer/StakingRewards/ReefStakingRewardsFetcher.swift index 33f2a26a8..cbc155ec5 100644 --- a/fearless/ApplicationLayer/StakingRewards/ReefStakingRewardsFetcher.swift +++ b/fearless/ApplicationLayer/StakingRewards/ReefStakingRewardsFetcher.swift @@ -1,5 +1,6 @@ import Foundation import SSFModels +import SSFNetwork struct ReefStakingPageReponse { let result: [RewardOrSlashData] @@ -47,7 +48,7 @@ final class ReefStakingRewardsFetcher { baseURL: blockExplorer.url, query: queryString(address: address, offset: max(1, rewards.count)) ) - let worker = NetworkWorker() + let worker = NetworkWorkerImpl() let response: GraphQLResponse = try await worker.performRequest(with: request) switch response { diff --git a/fearless/ApplicationLayer/StakingRewards/Requests/StakingRewardsRequest.swift b/fearless/ApplicationLayer/StakingRewards/Requests/StakingRewardsRequest.swift index bc23af7a6..db644a0ff 100644 --- a/fearless/ApplicationLayer/StakingRewards/Requests/StakingRewardsRequest.swift +++ b/fearless/ApplicationLayer/StakingRewards/Requests/StakingRewardsRequest.swift @@ -2,6 +2,7 @@ import Foundation import SSFUtils import RobinHood import SSFModels +import SSFNetwork class StakingRewardsRequest: RequestConfig { init( diff --git a/fearless/ApplicationLayer/StakingRewards/SoraStakingRewardsFetcher.swift b/fearless/ApplicationLayer/StakingRewards/SoraStakingRewardsFetcher.swift index 6978b8dc9..57e20714b 100644 --- a/fearless/ApplicationLayer/StakingRewards/SoraStakingRewardsFetcher.swift +++ b/fearless/ApplicationLayer/StakingRewards/SoraStakingRewardsFetcher.swift @@ -1,6 +1,7 @@ import Foundation import SoraFoundation import SSFModels +import SSFNetwork final class SoraStakingRewardsFetcher { private let chain: ChainModel @@ -67,7 +68,7 @@ extension SoraStakingRewardsFetcher: StakingRewardsFetcher { baseURL: blockExplorer.url, query: queryString ) - let worker = NetworkWorker() + let worker = NetworkWorkerImpl() let response: GraphQLResponse = try await worker.performRequest(with: request) switch response { diff --git a/fearless/ApplicationLayer/StakingRewards/SubqueryStakingRewardsFetcher.swift b/fearless/ApplicationLayer/StakingRewards/SubqueryStakingRewardsFetcher.swift index 350225a7b..a3a9dbdb7 100644 --- a/fearless/ApplicationLayer/StakingRewards/SubqueryStakingRewardsFetcher.swift +++ b/fearless/ApplicationLayer/StakingRewards/SubqueryStakingRewardsFetcher.swift @@ -1,5 +1,6 @@ import Foundation import SSFModels +import SSFNetwork final class SubqueryStakingRewardsFetcher { private let chain: ChainModel @@ -69,7 +70,7 @@ extension SubqueryStakingRewardsFetcher: StakingRewardsFetcher { baseURL: blockExplorer.url, query: queryString ) - let worker = NetworkWorker() + let worker = NetworkWorkerImpl() let response: GraphQLResponse = try await worker.performRequest(with: request) switch response { diff --git a/fearless/ApplicationLayer/StakingRewards/SubsquidStakingRewardsFetcher.swift b/fearless/ApplicationLayer/StakingRewards/SubsquidStakingRewardsFetcher.swift index 72ccd36c9..0055c65dc 100644 --- a/fearless/ApplicationLayer/StakingRewards/SubsquidStakingRewardsFetcher.swift +++ b/fearless/ApplicationLayer/StakingRewards/SubsquidStakingRewardsFetcher.swift @@ -1,6 +1,7 @@ import Foundation import SoraFoundation import SSFModels +import SSFNetwork final class SubsquidStakingRewardsFetcher { private let chain: ChainModel @@ -74,7 +75,7 @@ extension SubsquidStakingRewardsFetcher: StakingRewardsFetcher { baseURL: blockExplorer.url, query: queryString ) - let worker = NetworkWorker() + let worker = NetworkWorkerImpl() let response: GraphQLResponse = try await worker.performRequest(with: request) switch response { diff --git a/fearless/Common/Model/AccountStatistics.swift b/fearless/Common/Model/AccountStatistics.swift new file mode 100644 index 000000000..ded5750a3 --- /dev/null +++ b/fearless/Common/Model/AccountStatistics.swift @@ -0,0 +1,21 @@ +import Foundation + +struct AccountStatisticsResponse: Decodable { + let data: AccountStatistics? +} + +struct AccountStatistics: Decodable { + let score: Decimal? + let stats: AccountStatisticsData? +} + +struct AccountStatisticsData: Decodable { + let nativeBalanceUSD: Decimal? + let holdTokensBalanceUSD: Decimal? + let walletAge: Int? + let totalTransactions: Int? + let totalRejectedTransactions: Int? + let averageTransactionTime: Decimal? + let maxTransactionTime: Decimal? + let minTransactionTime: Decimal? +} diff --git a/fearless/CoreLayer/ComponentFactories/Network/NetworkClientFactory.swift b/fearless/CoreLayer/ComponentFactories/Network/NetworkClientFactory.swift deleted file mode 100644 index 90e4aaa01..000000000 --- a/fearless/CoreLayer/ComponentFactories/Network/NetworkClientFactory.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - -protocol NetworkClientFactory { - func buildNetworkClient(with type: NetworkClientType) -> NetworkClient -} - -final class BaseNetworkClientFactory: NetworkClientFactory { - func buildNetworkClient(with type: NetworkClientType) -> NetworkClient { - switch type { - case .plain: - return RESTNetworkClient(session: URLSession.shared) - case let .custom(client): - return client - } - } -} diff --git a/fearless/CoreLayer/ComponentFactories/Network/RequestConfiguratorFactory.swift b/fearless/CoreLayer/ComponentFactories/Network/RequestConfiguratorFactory.swift deleted file mode 100644 index 628eb5320..000000000 --- a/fearless/CoreLayer/ComponentFactories/Network/RequestConfiguratorFactory.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -enum RequestConfiguratorFactoryError: Error { - case requestTypeNotSupported -} - -protocol RequestConfiguratorFactory { - func buildRequestConfigurator(with type: NetworkRequestType, baseURL: URL) throws -> RequestConfigurator -} - -final class BaseRequestConfiguratorFactory: RequestConfiguratorFactory { - func buildRequestConfigurator(with type: NetworkRequestType, baseURL: URL) throws -> RequestConfigurator { - switch type { - case .plain: - return RESTRequestConfigurator(baseURL: baseURL) - case .multipart: - throw RequestConfiguratorFactoryError.requestTypeNotSupported - case let .custom(configurator): - return configurator - } - } -} diff --git a/fearless/CoreLayer/ComponentFactories/Network/RequestSignerFactory.swift b/fearless/CoreLayer/ComponentFactories/Network/RequestSignerFactory.swift deleted file mode 100644 index ab2dd3f2e..000000000 --- a/fearless/CoreLayer/ComponentFactories/Network/RequestSignerFactory.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -enum RequestSignerFactoryError: Error { - case signingTypeNotSupported -} - -protocol RequestSignerFactory { - func buildRequestSigner(with type: RequestSigningType) throws -> RequestSigner? -} - -final class BaseRequestSignerFactory: RequestSignerFactory { - func buildRequestSigner(with type: RequestSigningType) throws -> RequestSigner? { - switch type { - case .none: - return nil - case .bearer: - throw RequestSignerFactoryError.signingTypeNotSupported - case let .custom(signer): - return signer - } - } -} diff --git a/fearless/CoreLayer/ComponentFactories/Network/ResponseDecodersFactory.swift b/fearless/CoreLayer/ComponentFactories/Network/ResponseDecodersFactory.swift deleted file mode 100644 index 2a72c0a6d..000000000 --- a/fearless/CoreLayer/ComponentFactories/Network/ResponseDecodersFactory.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation -import SSFUtils - -protocol ResponseDecodersFactory { - func buildResponseDecoder(with type: ResponseDecoderType) -> any ResponseDecoder -} - -final class BaseResponseDecoderFactory: ResponseDecodersFactory { - func buildResponseDecoder(with type: ResponseDecoderType) -> any ResponseDecoder { - switch type { - case let .codable(jsonDecoder): - return JSONResponseDecoder(jsonDecoder: jsonDecoder) - case let .custom(decoder): - return decoder - } - } -} diff --git a/fearless/CoreLayer/CoreComponents/Network/Config/NetworkClientType.swift b/fearless/CoreLayer/CoreComponents/Network/Config/NetworkClientType.swift deleted file mode 100644 index 9024e92ae..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/Config/NetworkClientType.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -enum NetworkClientType { - case plain - case custom(client: NetworkClient) -} diff --git a/fearless/CoreLayer/CoreComponents/Network/Config/NetworkRequestType.swift b/fearless/CoreLayer/CoreComponents/Network/Config/NetworkRequestType.swift deleted file mode 100644 index aa4ecc10c..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/Config/NetworkRequestType.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -enum NetworkRequestType { - case plain - case multipart - case custom(configurator: RequestConfigurator) -} diff --git a/fearless/CoreLayer/CoreComponents/Network/Config/RequestConfig.swift b/fearless/CoreLayer/CoreComponents/Network/Config/RequestConfig.swift deleted file mode 100644 index a56870b74..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/Config/RequestConfig.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation - -class RequestConfig { - let baseURL: URL - let method: HTTPMethod - let endpoint: String? - var queryItems: [URLQueryItem]? - var headers: [HTTPHeader]? - var body: Data? - var timeout: TimeInterval? - - var requestType: NetworkRequestType = .plain - var signingType: RequestSigningType = .none - var networkClientType: NetworkClientType = .plain - var decoderType: ResponseDecoderType = .codable(jsonDecoder: JSONDecoder()) - - init( - baseURL: URL, - method: HTTPMethod, - endpoint: String?, - queryItems: [URLQueryItem]? = nil, - headers: [HTTPHeader]?, - body: Data?, - timeout: TimeInterval? = nil - ) { - self.baseURL = baseURL - self.method = method - self.endpoint = endpoint - self.queryItems = queryItems - self.headers = headers - self.body = body - self.timeout = timeout - } -} diff --git a/fearless/CoreLayer/CoreComponents/Network/Config/RequestSigningType.swift b/fearless/CoreLayer/CoreComponents/Network/Config/RequestSigningType.swift deleted file mode 100644 index 66896b924..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/Config/RequestSigningType.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -enum RequestSigningType { - case none - case bearer - case custom(signer: RequestSigner) -} diff --git a/fearless/CoreLayer/CoreComponents/Network/Config/ResponseDecoderType.swift b/fearless/CoreLayer/CoreComponents/Network/Config/ResponseDecoderType.swift deleted file mode 100644 index 6c72d6fc0..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/Config/ResponseDecoderType.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -enum ResponseDecoderType { - case codable(jsonDecoder: JSONDecoder = JSONDecoder()) - case custom(decoder: any ResponseDecoder) -} diff --git a/fearless/CoreLayer/CoreComponents/Network/Models/HTTPHeader.swift b/fearless/CoreLayer/CoreComponents/Network/Models/HTTPHeader.swift deleted file mode 100644 index 486372d57..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/Models/HTTPHeader.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -struct HTTPHeader: Codable { - let field: String - let value: String -} diff --git a/fearless/CoreLayer/CoreComponents/Network/Models/HTTPMethod.swift b/fearless/CoreLayer/CoreComponents/Network/Models/HTTPMethod.swift deleted file mode 100644 index 0bcf70e4a..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/Models/HTTPMethod.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -enum HTTPMethod: String { - case post = "POST" - case get = "GET" -} diff --git a/fearless/CoreLayer/CoreComponents/Network/Models/NetworkingError.swift b/fearless/CoreLayer/CoreComponents/Network/Models/NetworkingError.swift deleted file mode 100644 index 62887fecd..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/Models/NetworkingError.swift +++ /dev/null @@ -1,209 +0,0 @@ -import Foundation - -public struct NetworkingError: Error, LocalizedError { - public enum Status: Int { - case unknown = -1 - case networkUnreachable = 0 - - case unableToParseResponse = 1 - case unableToParseRequest = 2 - - // 1xx Informational - case continueError = 100 - case switchingProtocols = 101 - case processing = 102 - - // 2xx Success - case ok = 200 - case created = 201 - case accepted = 202 - case nonAuthoritativeInformation = 203 - case noContent = 204 - case resetContent = 205 - case partialContent = 206 - case multiStatus = 207 - case alreadyReported = 208 - case IMUsed = 226 - - // 3xx Redirection - case multipleChoices = 300 - case movedPermanently = 301 - case found = 302 - case seeOther = 303 - case notModified = 304 - case useProxy = 305 - case switchProxy = 306 - case temporaryRedirect = 307 - case permenantRedirect = 308 - - // 4xx Client Error - case badRequest = 400 - case unauthorized = 401 - case paymentRequired = 402 - case forbidden = 403 - case notFound = 404 - case methodNotAllowed = 405 - case notAcceptable = 406 - case proxyAuthenticationRequired = 407 - case requestTimeout = 408 - case conflict = 409 - case gone = 410 - case lengthRequired = 411 - case preconditionFailed = 412 - case payloadTooLarge = 413 - case uriTooLong = 414 - case unsupportedMediaType = 415 - case rangeNotSatisfiable = 416 - case expectationFailed = 417 - case teapot = 418 - case misdirectedRequest = 421 - case unprocessableEntity = 422 - case locked = 423 - case failedDependency = 424 - case upgradeRequired = 426 - case preconditionRequired = 428 - case tooManyRequests = 429 - case requestHeaderFieldsTooLarge = 431 - case unavailableForLegalReasons = 451 - - // 4xx nginx - case noResponse = 444 - case sslCertificateError = 495 - case sslCertificateRequired = 496 - case httpRequestSentToHTTPSPort = 497 - case clientClosedRequest = 499 - - // 5xx Server Error - case internalServerError = 500 - case notImplemented = 501 - case badGateway = 502 - case serviceUnavailable = 503 - case gatewayTimeout = 504 - case httpVersionNotSupported = 505 - case variantAlsoNegotiates = 506 - case insufficientStorage = 507 - case loopDetected = 508 - case notExtended = 510 - case networkAuthenticationRequired = 511 - - // domain - case cancelled = -999 - case badURL = -1000 - case timedOut = -1001 - case unsupportedURL = -1002 - case cannotFindHost = -1003 - case cannotConnectToHost = -1004 - case networkConnectionLost = -1005 - case dnsLookupFailed = -1006 - case httpTooManyRedirects = -1007 - case resourceUnavailable = -1008 - case notConnectedToInternet = -1009 - case redirectToNonExistentLocation = -1010 - case badServerResponse = -1011 - case userCancelledAuthentication = -1012 - case userAuthenticationRequired = -1013 - case zeroByteResource = -1014 - case cannotDecodeRawData = -1015 - case cannotDecodeContentData = -1016 - case cannotParseResponse = -1017 - case appTransportSecurityRequiresSecureConnection = -1022 - case fileDoesNotExist = -1100 - case fileIsDirectory = -1101 - case noPermissionsToReadFile = -1102 - case dataLengthExceedsMaximum = -1103 - - // SSL errors - case secureConnectionFailed = -1200 - case serverCertificateHasBadDate = -1201 - case serverCertificateUntrusted = -1202 - case serverCertificateHasUnknownRoot = -1203 - case serverCertificateNotYetValid = -1204 - case clientCertificateRejected = -1205 - case cclientCertificateRequired = -1206 - - case cannotLoadFromNetwork = -2000 - - // Download and file I/O errors - case cannotCreateFile = -3000 - case cannotOpenFile = -3001 - case cannotCloseFile = -3002 - case cannotWriteToFile = -3003 - case ccannotRemoveFile = -3004 - case cannotMoveFile = -3005 - case downloadDecodingFailedMidStream = -3006 - case downloadDecodingFailedToComplete = -3007 - } - - public var status: Status - public var code: Int { status.rawValue } - public var jsonPayload: Any? - - public init(errorCode: Int) { - status = Status(rawValue: errorCode) ?? .unknown - } - - public init(status: Status) { - self.status = status - } - - public init(error: Error) { - if let networkingError = error as? NetworkingError { - status = networkingError.status - jsonPayload = networkingError.jsonPayload - } else { - if let theError = error as? URLError { - status = Status(rawValue: theError.errorCode) ?? .unknown - } else { - status = .unknown - } - } - } - - // for LocalizedError protocol - public var errorDescription: String? { - "\(status)" - } -} - -extension NetworkingError: CustomStringConvertible { - public var description: String { - String(describing: status) - .replacingOccurrences( - of: "(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", - with: " ", - options: [.regularExpression] - ) - .capitalized - } -} - -public extension NetworkingError { - static var unableToParseResponse: NetworkingError { - NetworkingError(status: .unableToParseResponse) - } - - static var unableToParseRequest: NetworkingError { - NetworkingError(status: .unableToParseRequest) - } - - static var unknownError: NetworkingError { - NetworkingError(status: .unknown) - } -} - -public extension DecodingError { - var description: String? { - switch self { - case let .typeMismatch(_, value): - return "typeMismatch error: \(value.debugDescription) \(localizedDescription)" - case let .valueNotFound(_, value): - return "valueNotFound error: \(value.debugDescription) \(localizedDescription)" - case let .keyNotFound(_, value): - return "keyNotFound error: \(value.debugDescription) \(localizedDescription)" - case let .dataCorrupted(key): - return "dataCorrupted error at: \(key) \(localizedDescription)" - default: - return "decoding error: \(localizedDescription)" - } - } -} diff --git a/fearless/CoreLayer/CoreComponents/Network/NetworkClient/NetworkClient.swift b/fearless/CoreLayer/CoreComponents/Network/NetworkClient/NetworkClient.swift deleted file mode 100644 index 97d453ffb..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/NetworkClient/NetworkClient.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -protocol NetworkClient { - func perform(request: URLRequest) async -> Result -} diff --git a/fearless/CoreLayer/CoreComponents/Network/NetworkClient/RESTNetworkClient.swift b/fearless/CoreLayer/CoreComponents/Network/NetworkClient/RESTNetworkClient.swift deleted file mode 100644 index 6c24bcf81..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/NetworkClient/RESTNetworkClient.swift +++ /dev/null @@ -1,48 +0,0 @@ -import Foundation - -final class RESTNetworkClient { - private let session: URLSession - - init(session: URLSession) { - self.session = session - } - - private func processDataResponse( - data: Data, - response: URLResponse - ) -> Result { - guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { - return .failure(.init(status: .unknown)) - } - guard 200 ..< 299 ~= statusCode else { - return .failure(.init(errorCode: statusCode)) - } - - return .success(data) - } -} - -extension RESTNetworkClient: NetworkClient { - func perform(request: URLRequest) async -> Result { - var data: Data, response: URLResponse - - do { - (data, response) = try await withCheckedThrowingContinuation { continuation in - session.dataTask(with: request) { data, response, error in - if let error = error { - continuation.resume(throwing: error) - } else if let response = response, let data = data { - continuation.resume(returning: (data, response)) - } else { - continuation.resume(throwing: NetworkingError(status: .unknown)) - } - } - .resume() - } - } catch let error as NSError { - return .failure(.init(errorCode: error.code)) - } - - return processDataResponse(data: data, response: response) - } -} diff --git a/fearless/CoreLayer/CoreComponents/Network/NetworkWorker.swift b/fearless/CoreLayer/CoreComponents/Network/NetworkWorker.swift deleted file mode 100644 index 5f4272cef..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/NetworkWorker.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -final class NetworkWorker { - func performRequest(with config: RequestConfig) async throws -> T { - let requestConfigurator = try BaseRequestConfiguratorFactory().buildRequestConfigurator(with: config.requestType, baseURL: config.baseURL) - let requestSigner = try BaseRequestSignerFactory().buildRequestSigner(with: config.signingType) - let networkClient = BaseNetworkClientFactory().buildNetworkClient(with: config.networkClientType) - let responseDecoder = BaseResponseDecoderFactory().buildResponseDecoder(with: config.decoderType) - - var request = try requestConfigurator.buildRequest(with: config) - try requestSigner?.sign(request: &request, config: config) - let response = await networkClient.perform(request: request) - - switch response { - case let .success(response): - let decoded: T = try responseDecoder.decode(data: response) - return decoded - case let .failure(error): - throw error - } - } -} diff --git a/fearless/CoreLayer/CoreComponents/Network/RequestConfigurators/RESTRequestConfigurator.swift b/fearless/CoreLayer/CoreComponents/Network/RequestConfigurators/RESTRequestConfigurator.swift deleted file mode 100644 index a7dc33f0f..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/RequestConfigurators/RESTRequestConfigurator.swift +++ /dev/null @@ -1,52 +0,0 @@ -import Foundation - -enum RESTRequestConfiguratorError: Error { - case badURL -} - -final class RESTRequestConfigurator { - private let baseURL: URL - - init(baseURL: URL) { - self.baseURL = baseURL - } -} - -extension RESTRequestConfigurator: RequestConfigurator { - func buildRequest(with config: RequestConfig) throws -> URLRequest { - var urlComponents = URLComponents() - urlComponents.scheme = config.baseURL.scheme - urlComponents.host = config.baseURL.host - urlComponents.port = config.baseURL.port - urlComponents.path = config.baseURL.path - urlComponents.queryItems = config.queryItems - - guard var url = urlComponents.url else { - throw RESTRequestConfiguratorError.badURL - } - - if let endpoint = config.endpoint, let urlWithEndpoint = urlComponents.url?.appendingPathComponent(endpoint) { - url = urlWithEndpoint - } - - var urlRequest = URLRequest(url: url) - urlRequest.httpMethod = config.method.rawValue - urlRequest.httpBody = config.body - - if let timeout = config.timeout { - urlRequest.timeoutInterval = timeout - } - - config.headers?.forEach { - urlRequest.addValue($0.value, forHTTPHeaderField: $0.field) - } - - #if DEBUG - if let bodyData = config.body { - Logger.shared.debug("URL Request: \(urlComponents.url?.absoluteString) ; BODY: \n \(try JSONSerialization.jsonObject(with: bodyData))") - } - #endif - - return urlRequest - } -} diff --git a/fearless/CoreLayer/CoreComponents/Network/RequestConfigurators/RequestConfigurator.swift b/fearless/CoreLayer/CoreComponents/Network/RequestConfigurators/RequestConfigurator.swift deleted file mode 100644 index 6158a8cce..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/RequestConfigurators/RequestConfigurator.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -protocol RequestConfigurator { - func buildRequest(with config: RequestConfig) throws -> URLRequest -} diff --git a/fearless/CoreLayer/CoreComponents/Network/RequestSigners/AlchemyRequestSigner.swift b/fearless/CoreLayer/CoreComponents/Network/RequestSigners/AlchemyRequestSigner.swift deleted file mode 100644 index fdc1744a1..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/RequestSigners/AlchemyRequestSigner.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation - -final class AlchemyRequestSigner { - private let apiKey: String - - init(apiKey: String) { - self.apiKey = apiKey - } -} - -extension AlchemyRequestSigner: RequestSigner { - func sign(request: inout URLRequest, config: RequestConfig) throws { - guard - var urlString = request.url?.absoluteString, - let baseUrlBound = urlString.range(of: config.baseURL.absoluteString)?.upperBound - else { - throw RequestSignerError.badURL - } - - urlString.insert(contentsOf: apiKey, at: baseUrlBound) - request.url = URL(string: urlString) - } -} diff --git a/fearless/CoreLayer/CoreComponents/Network/RequestSigners/RequestSigner.swift b/fearless/CoreLayer/CoreComponents/Network/RequestSigners/RequestSigner.swift deleted file mode 100644 index 586757a20..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/RequestSigners/RequestSigner.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -enum RequestSignerError: Error { - case badURL -} - -protocol RequestSigner { - func sign(request: inout URLRequest, config: RequestConfig) throws -} diff --git a/fearless/CoreLayer/CoreComponents/Network/ResponseDecoders/JSONResponseDecoder.swift b/fearless/CoreLayer/CoreComponents/Network/ResponseDecoders/JSONResponseDecoder.swift deleted file mode 100644 index ee11e1aa5..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/ResponseDecoders/JSONResponseDecoder.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation -import SSFUtils - -enum JSONResponseDecoderError: Error { - case typeNotDecodable -} - -final class JSONResponseDecoder: ResponseDecoder { - private let jsonDecoder: JSONDecoder - - init(jsonDecoder: JSONDecoder = JSONDecoder()) { - self.jsonDecoder = jsonDecoder - } - - func decode(data: Data) throws -> T { - guard let type = T.self as? Decodable.Type else { - throw JSONResponseDecoderError.typeNotDecodable - } - - let obj = try jsonDecoder.decode(type, from: data) - - guard let decoded = obj as? T else { - throw JSONResponseDecoderError.typeNotDecodable - } - - return decoded - } -} diff --git a/fearless/CoreLayer/CoreComponents/Network/ResponseDecoders/ResponseDecoder.swift b/fearless/CoreLayer/CoreComponents/Network/ResponseDecoders/ResponseDecoder.swift deleted file mode 100644 index f0566401a..000000000 --- a/fearless/CoreLayer/CoreComponents/Network/ResponseDecoders/ResponseDecoder.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -protocol ResponseDecoder { - func decode(data: Data) throws -> T -} diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift b/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift new file mode 100644 index 000000000..08ec322cc --- /dev/null +++ b/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift @@ -0,0 +1,24 @@ +import UIKit +import SoraFoundation + +final class AccountStatisticsAssembly { + static func configureModule() -> AccountStatisticsModuleCreationResult? { + let localizationManager = LocalizationManager.shared + + let interactor = AccountStatisticsInteractor() + let router = AccountStatisticsRouter() + + let presenter = AccountStatisticsPresenter( + interactor: interactor, + router: router, + localizationManager: localizationManager + ) + + let view = AccountStatisticsViewController( + output: presenter, + localizationManager: localizationManager + ) + + return (view, presenter) + } +} diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift b/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift new file mode 100644 index 000000000..ed211c1bc --- /dev/null +++ b/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift @@ -0,0 +1,17 @@ +import UIKit + +protocol AccountStatisticsInteractorOutput: AnyObject {} + +final class AccountStatisticsInteractor { + // MARK: - Private properties + + private weak var output: AccountStatisticsInteractorOutput? +} + +// MARK: - AccountStatisticsInteractorInput + +extension AccountStatisticsInteractor: AccountStatisticsInteractorInput { + func setup(with output: AccountStatisticsInteractorOutput) { + self.output = output + } +} diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift b/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift new file mode 100644 index 000000000..c6d836819 --- /dev/null +++ b/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift @@ -0,0 +1,51 @@ +import Foundation +import SoraFoundation + +protocol AccountStatisticsViewInput: ControllerBackedProtocol {} + +protocol AccountStatisticsInteractorInput: AnyObject { + func setup(with output: AccountStatisticsInteractorOutput) +} + +final class AccountStatisticsPresenter { + // MARK: Private properties + + private weak var view: AccountStatisticsViewInput? + private let router: AccountStatisticsRouterInput + private let interactor: AccountStatisticsInteractorInput + + // MARK: - Constructors + + init( + interactor: AccountStatisticsInteractorInput, + router: AccountStatisticsRouterInput, + localizationManager: LocalizationManagerProtocol + ) { + self.interactor = interactor + self.router = router + self.localizationManager = localizationManager + } + + // MARK: - Private methods +} + +// MARK: - AccountStatisticsViewOutput + +extension AccountStatisticsPresenter: AccountStatisticsViewOutput { + func didLoad(view: AccountStatisticsViewInput) { + self.view = view + interactor.setup(with: self) + } +} + +// MARK: - AccountStatisticsInteractorOutput + +extension AccountStatisticsPresenter: AccountStatisticsInteractorOutput {} + +// MARK: - Localizable + +extension AccountStatisticsPresenter: Localizable { + func applyLocalization() {} +} + +extension AccountStatisticsPresenter: AccountStatisticsModuleInput {} diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsProtocols.swift b/fearless/Modules/AccountStatistics/AccountStatisticsProtocols.swift new file mode 100644 index 000000000..8b3af8c89 --- /dev/null +++ b/fearless/Modules/AccountStatistics/AccountStatisticsProtocols.swift @@ -0,0 +1,10 @@ +typealias AccountStatisticsModuleCreationResult = ( + view: AccountStatisticsViewInput, + input: AccountStatisticsModuleInput +) + +protocol AccountStatisticsRouterInput: AnyObject {} + +protocol AccountStatisticsModuleInput: AnyObject {} + +protocol AccountStatisticsModuleOutput: AnyObject {} diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsRouter.swift b/fearless/Modules/AccountStatistics/AccountStatisticsRouter.swift new file mode 100644 index 000000000..3b59c6e6e --- /dev/null +++ b/fearless/Modules/AccountStatistics/AccountStatisticsRouter.swift @@ -0,0 +1,3 @@ +import Foundation + +final class AccountStatisticsRouter: AccountStatisticsRouterInput {} diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift b/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift new file mode 100644 index 000000000..f6475ce08 --- /dev/null +++ b/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift @@ -0,0 +1,55 @@ +import UIKit +import SoraFoundation + +protocol AccountStatisticsViewOutput: AnyObject { + func didLoad(view: AccountStatisticsViewInput) +} + +final class AccountStatisticsViewController: UIViewController, ViewHolder { + typealias RootViewType = AccountStatisticsViewLayout + + // MARK: Private properties + + private let output: AccountStatisticsViewOutput + + // MARK: - Constructor + + init( + output: AccountStatisticsViewOutput, + localizationManager: LocalizationManagerProtocol? + ) { + self.output = output + super.init(nibName: nil, bundle: nil) + self.localizationManager = localizationManager + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Life cycle + + override func loadView() { + view = AccountStatisticsViewLayout() + } + + override func viewDidLoad() { + super.viewDidLoad() + output.didLoad(view: self) + } + + // MARK: - Private methods +} + +// MARK: - AccountStatisticsViewInput + +extension AccountStatisticsViewController: AccountStatisticsViewInput {} + +// MARK: - Localizable + +extension AccountStatisticsViewController: Localizable { + func applyLocalization() { + rootView.locale = selectedLocale + } +} diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift b/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift new file mode 100644 index 000000000..168cf2703 --- /dev/null +++ b/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift @@ -0,0 +1,175 @@ +import UIKit +import Cosmos + +final class AccountStatisticsViewLayout: UIView { + let topBar: BaseNavigationBar = { + let bar = BaseNavigationBar() + bar.set(.present) + return bar + }() + + let scrollView = UIScrollView() + let backgroundView = UIView() + + let ratingView: CosmosView = { + var settings = CosmosSettings() + settings.starSize = 30 + settings.fillMode = .full + return CosmosView(settings: settings) + }() + + let descriptionLabel: UILabel = { + let label = UILabel() + label.font = .p2Paragraph + label.textColor = R.color.colorLightGray() + return label + }() + + let scoreLabel: UILabel = { + let label = UILabel() + label.font = .h1Title + label.textColor = .white + return label + }() + + let addressView = CopyableLabelView() + + let statsBackgroundView = TriangularedView() + + let stackView = UIFactory.default.createVerticalStackView(spacing: 8) + + let updatedView = makeRowView() + let nativeBalanceUsdView = makeRowView() + let holdTokensUsdView = makeRowView() + let walletAgeView = makeRowView() + let totalTransactionsView = makeRowView() + let rejectedTransactionsView = makeRowView() + let avgTransactionTimeView = makeRowView() + let maxTransactionTimeView = makeRowView() + let minTransactionTimeView = makeRowView() + + let closeButton = TriangularedButton() + + var locale: Locale = .current { + didSet { + applyLocalization() + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubviews() + setupConstraints() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Private methods + + private func addSubviews() { + addSubview(topBar) + addSubview(scrollView) + addSubview(closeButton) + + scrollView.addSubview(statsBackgroundView) + statsBackgroundView.addSubview(stackView) + + [updatedView, + nativeBalanceUsdView, + holdTokensUsdView, + walletAgeView, + totalTransactionsView, + rejectedTransactionsView, + avgTransactionTimeView, + maxTransactionTimeView, + minTransactionTimeView].forEach { + stackView.addArrangedSubview($0) + setupRowViewConstraints($0) + } + } + + private func setupConstraints() { + topBar.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview() + make.height.equalTo(56) + } + + closeButton.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(16) + make.height.equalTo(48) + make.bottom.equalTo(safeAreaInsets).inset(16) + } + + scrollView.snp.makeConstraints { make in + make.top.equalTo(topBar.snp.bottom) + make.bottom.equalTo(closeButton).offset(16) + make.leading.trailing.equalToSuperview() + } + + statsBackgroundView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + stackView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + ratingView.snp.makeConstraints { make in + make.top.equalTo(topBar.snp.bottom).offset(16) + make.centerX.equalToSuperview() + make.height.equalTo(30) + } + + descriptionLabel.snp.makeConstraints { make in + make.top.equalTo(ratingView.snp.bottom).offset(8) + make.leading.trailing.equalToSuperview().inset(16) + } + + scoreLabel.snp.makeConstraints { make in + make.top.equalTo(descriptionLabel.snp.bottom).offset(8) + make.centerX.equalToSuperview() + } + + addressView.snp.makeConstraints { make in + make.top.equalTo(scoreLabel.snp.bottom).offset(8) + make.centerX.equalToSuperview() + } + } + + private func applyLocalization() { + topBar.setTitle(R.string.localizable.accountStatsTitle(preferredLanguages: locale.rLanguages)) + descriptionLabel.text = R.string.localizable.accountStatsDescriptionText(preferredLanguages: locale.rLanguages) + updatedView.titleLabel.text = R.string.localizable.accountStatsUpdatedTitle(preferredLanguages: locale.rLanguages) + nativeBalanceUsdView.titleLabel.text = R.string.localizable.accountStatsNativeBalanceUsdTitle(preferredLanguages: locale.rLanguages) + holdTokensUsdView.titleLabel.text = R.string.localizable.accountStatsHoldTokensUsdTitle(preferredLanguages: locale.rLanguages) + walletAgeView.titleLabel.text = R.string.localizable.accountStatsWalletAgeTitle(preferredLanguages: locale.rLanguages) + totalTransactionsView.titleLabel.text = R.string.localizable.accountStatsTotalTransactionsTitle(preferredLanguages: locale.rLanguages) + rejectedTransactionsView.titleLabel.text = R.string.localizable.accountStatsRejectedTransactionsTitle(preferredLanguages: locale.rLanguages) + avgTransactionTimeView.titleLabel.text = R.string.localizable.accountStatsAvgTransactionTimeTitle(preferredLanguages: locale.rLanguages) + maxTransactionTimeView.titleLabel.text = R.string.localizable.accountStatsMaxTransactionTimeTitle(preferredLanguages: locale.rLanguages) + minTransactionTimeView.titleLabel.text = R.string.localizable.accountStatsMinTransactionsTimeTitle(preferredLanguages: locale.rLanguages) + } + + private func setupRowViewConstraints(_ view: UIView) { + view.snp.makeConstraints { make in + make.height.equalTo(48) + make.leading.trailing.equalToSuperview().inset(16) + } + } + + private static func makeRowView() -> TitleValueView { + let view = TitleValueView() + view.titleLabel.font = .p1Paragraph + view.titleLabel.textColor = R.color.colorWhite() + view.valueLabel.font = .p1Paragraph + view.valueLabel.textColor = R.color.colorWhite() + view.borderView.isHidden = true + view.equalsLabelsWidth = true + view.valueLabel.lineBreakMode = .byTruncatingTail + return view + } +} diff --git a/fearless/Modules/LiquidityPools/LiquidityPoolDetails/Resources/chains.json b/fearless/Modules/LiquidityPools/LiquidityPoolDetails/Resources/chains.json index 9beb1d36a..cee499f26 100644 --- a/fearless/Modules/LiquidityPools/LiquidityPoolDetails/Resources/chains.json +++ b/fearless/Modules/LiquidityPools/LiquidityPoolDetails/Resources/chains.json @@ -3499,7 +3499,7 @@ "type": "normal" }], "nodes": [{ - "url": "wss://eden-rpc.dwellir.com", + "url": "wss://nodle-rpc.dwellir.com", "name": "Dwellir node" }, { @@ -5073,7 +5073,7 @@ }, "staking": { "type" : "sora", - "url": "https://sora.squids.live/sora/v/v7/graphql" + "url": "https://squid.subsquid.io/sora/v/v5/graphql" }, "pricing": { "type": "sora", diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift index e6e0ee09f..13387c1ad 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift @@ -2,6 +2,7 @@ import UIKit import SoraFoundation import RobinHood import SSFUtils +import SSFNetwork final class WalletMainContainerAssembly { static func configureModule( @@ -49,7 +50,7 @@ final class WalletMainContainerAssembly { walletRepository: AnyDataProviderRepository(accountRepository), stashItemRepository: substrateRepositoryFactory.createStashItemRepository() ) - + let accountStatisticsFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) let interactor = WalletMainContainerInteractor( accountRepository: AnyDataProviderRepository(accountRepository), chainRepository: AnyDataProviderRepository(chainRepository), @@ -58,7 +59,8 @@ final class WalletMainContainerAssembly { eventCenter: EventCenter.shared, deprecatedAccountsCheckService: deprecatedAccountsCheckService, applicationHandler: ApplicationHandler(), - walletConnectService: walletConnect + walletConnectService: walletConnect, + accountStatisticsFetcher: accountStatisticsFetcher ) let router = WalletMainContainerRouter() diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift index b60df973b..9fc87b498 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift @@ -16,6 +16,7 @@ final class WalletMainContainerInteractor { private let deprecatedAccountsCheckService: DeprecatedControllerStashAccountCheckServiceProtocol private let applicationHandler: ApplicationHandler private let walletConnectService: WalletConnectService + private let accountStatisticsFetcher: AccountStatisticsFetching // MARK: - Constructor @@ -27,7 +28,8 @@ final class WalletMainContainerInteractor { eventCenter: EventCenterProtocol, deprecatedAccountsCheckService: DeprecatedControllerStashAccountCheckServiceProtocol, applicationHandler: ApplicationHandler, - walletConnectService: WalletConnectService + walletConnectService: WalletConnectService, + accountStatisticsFetcher: AccountStatisticsFetching ) { self.wallet = wallet self.chainRepository = chainRepository @@ -37,11 +39,31 @@ final class WalletMainContainerInteractor { self.deprecatedAccountsCheckService = deprecatedAccountsCheckService self.applicationHandler = applicationHandler self.walletConnectService = walletConnectService + self.accountStatisticsFetcher = accountStatisticsFetcher applicationHandler.delegate = self } // MARK: - Private methods + private func fetchAccountStats() { + guard let ethereumAccountId = wallet.ethereumAddress else { + return + } + + let address = ethereumAccountId.toHex(includePrefix: true) + + Task { + do { + let stream = try await accountStatisticsFetcher.subscribeForStatistics(address: address, cacheOptions: .onAll) + for try await statistics in stream { + print("Account statistics: ", statistics.value?.data) + } + } catch { + print("Account statistics fetching error: ", error) + } + } + } + private func fetchNetworkManagmentFilter() { guard let identifier = wallet.networkManagmentFilter else { output?.didReceiveSelected(tuple: (select: .all, chains: [])) @@ -108,6 +130,7 @@ extension WalletMainContainerInteractor: WalletMainContainerInteractorInput { self.output = output eventCenter.add(observer: self, dispatchIn: .main) fetchNetworkManagmentFilter() + fetchAccountStats() } func walletConnect(uri: String) async throws { diff --git a/fearless/en.lproj/Localizable.strings b/fearless/en.lproj/Localizable.strings index abef7e04f..0462274de 100644 --- a/fearless/en.lproj/Localizable.strings +++ b/fearless/en.lproj/Localizable.strings @@ -64,6 +64,17 @@ "account.needed.message" = "You don’t have account for this network, you can create or import an account."; "account.needed.title" = "Account needed"; "account.option" = "Account option"; +"account.stats.avg.transaction.time.title" = "Avg. transaction time"; +"account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.hold.tokens.usd.title" = "Hold tokens USD"; +"account.stats.max.transaction.time.title" = "Max transaction time"; +"account.stats.min.transactions.time.title" = "Min transaction time"; +"account.stats.native.balance.usd.title" = "Native balance USD"; +"account.stats.rejected.transactions.title" = "Rejected transactions"; +"account.stats.title" = "Your score"; +"account.stats.total.transactions.title" = "Total transactions"; +"account.stats.updated.title" = "Updated"; +"account.stats.wallet.age.title" = "Wallet age"; "account.template" = "%s account"; "account.unique.secret" = "Accounts with unique secrets"; "accounts.add.account" = "Add an account"; @@ -473,6 +484,8 @@ Seed: %@"; "lp.apy.alert.title" = "Strategic Bonus APY"; "lp.apy.title" = "Strategic Bonus APY"; "lp.available.pools.title" = "Available pools"; +"lp.banner.action.details.title" = "Show details"; +"lp.banner.text" = "Invest your funds in Liquidity\npools and receive rewards"; "lp.confirm.liquidity.screen.title" = "Confirm Liquidity"; "lp.confirm.liquidity.warning.text" = "Output is estimated. If the price changes more than 0.5% your transaction will revert."; "lp.network.fee.alert.text" = "Network fee is used to ensure SORA system’s growth and stable performance."; @@ -535,6 +548,7 @@ This is your transaction hash:"; "nft.collection.title" = "Collection"; "nft.creator.title" = "Creator"; "nft.list.empty.message" = "There aren't any NFTs yet. Buy or mint NFTs to see them here."; +"nft.load.error" = "Failed to load NFTs"; "nft.owner.title" = "Owned"; "nft.share.address" = "My public address to receive: %s"; "nft.spam.warning" = "Beware of a scam/spam NFT collection – verify authenticity before engaging. Stay safe!"; @@ -1189,4 +1203,4 @@ belongs to the right network"; "your.validators.change.validators.title" = "Change validators"; "your.validators.stop.nominating.title" = "Stop nominating"; "your.validators.validator.total.stake" = "Total staked: %@"; -"сurrencies.stub.text" = "Currencies"; +"сurrencies.stub.text" = "Currencies"; \ No newline at end of file diff --git a/fearless/id.lproj/Localizable.strings b/fearless/id.lproj/Localizable.strings index ee4a5987f..889a09de0 100644 --- a/fearless/id.lproj/Localizable.strings +++ b/fearless/id.lproj/Localizable.strings @@ -64,6 +64,17 @@ "account.needed.message" = "Anda tidak memiliki akun untuk jaringan ini, Anda bisa membuat atau mengimpor akun."; "account.needed.title" = "Akun dibutuhkan"; "account.option" = "Opsi akun"; +"account.stats.avg.transaction.time.title" = "Avg. transaction time"; +"account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.hold.tokens.usd.title" = "Hold tokens USD"; +"account.stats.max.transaction.time.title" = "Max transaction time"; +"account.stats.min.transactions.time.title" = "Min transaction time"; +"account.stats.native.balance.usd.title" = "Native balance USD"; +"account.stats.rejected.transactions.title" = "Rejected transactions"; +"account.stats.title" = "Your score"; +"account.stats.total.transactions.title" = "Total transactions"; +"account.stats.updated.title" = "Updated"; +"account.stats.wallet.age.title" = "Wallet age"; "account.template" = "%s Akun"; "account.unique.secret" = "Akun dengan rahasia unik"; "accounts.add.account" = "Tambah akun"; @@ -472,6 +483,8 @@ Seed: %@"; "lp.apy.alert.title" = "Strategic Bonus APY"; "lp.apy.title" = "Strategic Bonus APY"; "lp.available.pools.title" = "Available pools"; +"lp.banner.action.details.title" = "Show details"; +"lp.banner.text" = "Invest your funds in Liquidity\npools and receive rewards"; "lp.confirm.liquidity.screen.title" = "Confirm Liquidity"; "lp.confirm.liquidity.warning.text" = "Output is estimated. If the price changes more than 0.5% your transaction will revert."; "lp.network.fee.alert.text" = "Network fee is used to ensure SORA system’s growth and stable performance."; @@ -528,6 +541,7 @@ Seed: %@"; "nft.collection.title" = "Koleksi"; "nft.creator.title" = "Pencipta"; "nft.list.empty.message" = "Belum ada NFT. Beli atau cetak NFT untuk melihatnya di sini."; +"nft.load.error" = "Failed to load NFTs"; "nft.owner.title" = "Dimiliki"; "nft.share.address" = "Alamat publik saya untuk menerima:%s"; "nft.spam.warning" = "Beware of a scam/spam NFT collection – verify authenticity before engaging. Stay safe!"; @@ -1173,4 +1187,4 @@ akan muncul di sini"; "your.validators.change.validators.title" = "Ubah validator"; "your.validators.stop.nominating.title" = "berhenti mencalonkan diri"; "your.validators.validator.total.stake" = "Total ditaruhkan: %@"; -"сurrencies.stub.text" = "Mata uang"; +"сurrencies.stub.text" = "Mata uang"; \ No newline at end of file diff --git a/fearless/ja.lproj/Localizable.strings b/fearless/ja.lproj/Localizable.strings index e4a98e253..67286f7fb 100644 --- a/fearless/ja.lproj/Localizable.strings +++ b/fearless/ja.lproj/Localizable.strings @@ -64,6 +64,17 @@ "account.needed.message" = "このネットワークのアカウントがありません。アカウントを作成またはインポートできます"; "account.needed.title" = "アカウントが必要"; "account.option" = "アカウントオプション"; +"account.stats.avg.transaction.time.title" = "Avg. transaction time"; +"account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.hold.tokens.usd.title" = "Hold tokens USD"; +"account.stats.max.transaction.time.title" = "Max transaction time"; +"account.stats.min.transactions.time.title" = "Min transaction time"; +"account.stats.native.balance.usd.title" = "Native balance USD"; +"account.stats.rejected.transactions.title" = "Rejected transactions"; +"account.stats.title" = "Your score"; +"account.stats.total.transactions.title" = "Total transactions"; +"account.stats.updated.title" = "Updated"; +"account.stats.wallet.age.title" = "Wallet age"; "account.template" = "%sアカウント"; "account.unique.secret" = "一意のシークレットを持つアカウント"; "accounts.add.account" = "アカウントを追加"; @@ -472,6 +483,8 @@ "lp.apy.alert.title" = "戦略的ボーナス年利"; "lp.apy.title" = "戦略的ボーナス年利"; "lp.available.pools.title" = "利用可能なプール"; +"lp.banner.action.details.title" = "Show details"; +"lp.banner.text" = "Invest your funds in Liquidity\npools and receive rewards"; "lp.confirm.liquidity.screen.title" = "流動性を確認"; "lp.confirm.liquidity.warning.text" = "出力は推定です。価格が0.5%以上変動した場合、取引は元に戻されます。"; "lp.network.fee.alert.text" = "ネットワーク手数料はSORAシステムの成長と安定したパフォーマンスを確保するために使用されます。"; @@ -534,6 +547,7 @@ "nft.collection.title" = "コレクション"; "nft.creator.title" = "作成者"; "nft.list.empty.message" = "There aren't any NFTs yet. Buy or mint NFTs to see them here."; +"nft.load.error" = "Failed to load NFTs"; "nft.owner.title" = "所有"; "nft.share.address" = "受信するパブリック・アドレス: %s"; "nft.spam.warning" = "Beware of a scam/spam NFT collection – verify authenticity before engaging. Stay safe!"; @@ -1179,4 +1193,4 @@ "your.validators.change.validators.title" = "バリデーターの変更"; "your.validators.stop.nominating.title" = "ノミネートを中止"; "your.validators.validator.total.stake" = "総ステーク:%@"; -"сurrencies.stub.text" = "通貨"; +"сurrencies.stub.text" = "通貨"; \ No newline at end of file diff --git a/fearless/pt.lproj/Localizable.strings b/fearless/pt.lproj/Localizable.strings index 1cef5a69c..18d5ca956 100644 --- a/fearless/pt.lproj/Localizable.strings +++ b/fearless/pt.lproj/Localizable.strings @@ -64,6 +64,17 @@ "account.needed.message" = "Não há uma conta nesta rede, pode criar ou importar uma conta."; "account.needed.title" = "Conta necessária"; "account.option" = "Opção de conta"; +"account.stats.avg.transaction.time.title" = "Avg. transaction time"; +"account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.hold.tokens.usd.title" = "Hold tokens USD"; +"account.stats.max.transaction.time.title" = "Max transaction time"; +"account.stats.min.transactions.time.title" = "Min transaction time"; +"account.stats.native.balance.usd.title" = "Native balance USD"; +"account.stats.rejected.transactions.title" = "Rejected transactions"; +"account.stats.title" = "Your score"; +"account.stats.total.transactions.title" = "Total transactions"; +"account.stats.updated.title" = "Updated"; +"account.stats.wallet.age.title" = "Wallet age"; "account.template" = "%s conta"; "account.unique.secret" = "Contas com segredos exclusivos"; "accounts.add.account" = "Adicionar conta"; @@ -472,6 +483,8 @@ Seed: %@"; "lp.apy.alert.title" = "Strategic Bonus APY"; "lp.apy.title" = "Strategic Bonus APY"; "lp.available.pools.title" = "Available pools"; +"lp.banner.action.details.title" = "Show details"; +"lp.banner.text" = "Invest your funds in Liquidity\npools and receive rewards"; "lp.confirm.liquidity.screen.title" = "Confirm Liquidity"; "lp.confirm.liquidity.warning.text" = "Output is estimated. If the price changes more than 0.5% your transaction will revert."; "lp.network.fee.alert.text" = "Network fee is used to ensure SORA system’s growth and stable performance."; @@ -534,6 +547,7 @@ Este é o hash da sua transação:"; "nft.collection.title" = "Coleção"; "nft.creator.title" = "Creator"; "nft.list.empty.message" = "There aren't any NFTs yet. Buy or mint NFTs to see them here."; +"nft.load.error" = "Failed to load NFTs"; "nft.owner.title" = "Possuído"; "nft.share.address" = "My public address to receive: %s"; "nft.spam.warning" = "Beware of a scam/spam NFT collection – verify authenticity before engaging. Stay safe!"; diff --git a/fearless/ru.lproj/Localizable.strings b/fearless/ru.lproj/Localizable.strings index 2792ec5f1..d65408e06 100644 --- a/fearless/ru.lproj/Localizable.strings +++ b/fearless/ru.lproj/Localizable.strings @@ -64,6 +64,17 @@ "account.needed.message" = "У вас нет учетной записи в этой сети, вы можете создать или импортировать учетную запись."; "account.needed.title" = "Необходим аккаунт"; "account.option" = "Account option"; +"account.stats.avg.transaction.time.title" = "Avg. transaction time"; +"account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.hold.tokens.usd.title" = "Hold tokens USD"; +"account.stats.max.transaction.time.title" = "Max transaction time"; +"account.stats.min.transactions.time.title" = "Min transaction time"; +"account.stats.native.balance.usd.title" = "Native balance USD"; +"account.stats.rejected.transactions.title" = "Rejected transactions"; +"account.stats.title" = "Your score"; +"account.stats.total.transactions.title" = "Total transactions"; +"account.stats.updated.title" = "Updated"; +"account.stats.wallet.age.title" = "Wallet age"; "account.template" = "%s аккаунт"; "account.unique.secret" = "Аккаунты с измененным ключом"; "accounts.add.account" = "Добавить аккаунт"; @@ -469,26 +480,28 @@ Euro cash"; "label.testnet" = "Testnet"; "language.title" = "Язык"; "learn.more.about.crowdloans" = "Узнайте больше о Crowdloans"; -"lp.apy.alert.text" = "Farming reward for liquidity provision"; -"lp.apy.alert.title" = "Strategic Bonus APY"; -"lp.apy.title" = "Strategic Bonus APY"; -"lp.available.pools.title" = "Available pools"; -"lp.confirm.liquidity.screen.title" = "Confirm Liquidity"; -"lp.confirm.liquidity.warning.text" = "Output is estimated. If the price changes more than 0.5% your transaction will revert."; -"lp.network.fee.alert.text" = "Network fee is used to ensure SORA system’s growth and stable performance."; -"lp.network.fee.alert.title" = "Network fee"; -"lp.pool.details.title" = "Pool details"; -"lp.pool.remove.warning.text" = "Removing pool tokens converts your position back into underlying tokens at the current rate, proportional to your share of the pool. Accrued fees are included in the amounts you receive."; -"lp.pool.remove.warning.title" = "NOTE"; -"lp.remove.button.title" = "Remove Liquidity"; -"lp.remove.liquidity.screen.title" = "Remove Liquidity"; -"lp.reward.token.text" = "Earn %@"; -"lp.reward.token.title" = "Rewards Payout In"; -"lp.slippage.title" = "Slippage"; -"lp.supply.button.title" = "Supply Liquidity"; -"lp.supply.liquidity.screen.title" = "Supply Liquidity"; -"lp.token.pooled.text" = "Your %@ Pooled"; -"lp.user.pools.title" = "User pools"; +"lp.apy.alert.text" = "Вознаграждение за предоставление ликвидности"; +"lp.apy.alert.title" = "Стратегический бонус APY"; +"lp.apy.title" = "Стратегический бонус APY"; +"lp.available.pools.title" = "Доступные пулы"; +"lp.banner.action.details.title" = "Show details"; +"lp.banner.text" = "Invest your funds in Liquidity\npools and receive rewards"; +"lp.confirm.liquidity.screen.title" = "Подтвердите ликвидность"; +"lp.confirm.liquidity.warning.text" = "Оценка результата. Если цена изменится более чем на 0.5%, ваша транзакция будет отменена"; +"lp.network.fee.alert.text" = "Комиссия сети используется для обеспечения роста и стабильной работы системы SORA."; +"lp.network.fee.alert.title" = "Комиссия сети"; +"lp.pool.details.title" = "Детали пула ликвидности"; +"lp.pool.remove.warning.text" = "Удаление токенов из пула конвертирует вашу позицию обратно в исходные токены по текущему курсу, пропорционально вашей доле в пуле. Начисленные комиссии включены в получаемые вами суммы."; +"lp.pool.remove.warning.title" = "ПРИМЕЧАНИЕ"; +"lp.remove.button.title" = "Убрать ликвидность"; +"lp.remove.liquidity.screen.title" = "Убрать ликвидность"; +"lp.reward.token.text" = "Заработать %@"; +"lp.reward.token.title" = "Выплата вознаграждений в"; +"lp.slippage.title" = "Проскальзывание"; +"lp.supply.button.title" = "Добавить ликвидность"; +"lp.supply.liquidity.screen.title" = "Добавить ликвидность"; +"lp.token.pooled.text" = "Ваши %@ добавлены в пул"; +"lp.user.pools.title" = "Пулы пользователя"; "manage.assets.account.missing.text" = "Добавить аккаунт..."; "manage.assets.search.hint" = "Поиск по токену"; "members.common" = "Участники"; @@ -532,6 +545,7 @@ Euro cash"; "nft.collection.title" = "Collection"; "nft.creator.title" = "Creator"; "nft.list.empty.message" = "There aren't any NFTs yet. Buy or mint NFTs to see them here."; +"nft.load.error" = "Failed to load NFTs"; "nft.owner.title" = "Owned"; "nft.share.address" = "My public address to receive: %s"; "nft.spam.warning" = "Beware of a scam/spam NFT collection – verify authenticity before engaging. Stay safe!"; @@ -1187,4 +1201,4 @@ Euro cash"; "your.validators.change.validators.title" = "Изменить валидаторов"; "your.validators.stop.nominating.title" = "Stop nominating"; "your.validators.validator.total.stake" = "Всего в стейкинге: %@"; -"сurrencies.stub.text" = "Токены"; +"сurrencies.stub.text" = "Токены"; \ No newline at end of file diff --git a/fearless/tr.lproj/Localizable.strings b/fearless/tr.lproj/Localizable.strings index a41a58441..6e6bad43e 100644 --- a/fearless/tr.lproj/Localizable.strings +++ b/fearless/tr.lproj/Localizable.strings @@ -64,6 +64,17 @@ "account.needed.message" = "Bu ağ için hesabınız yok, hesap oluşturabilir veya içe aktarabilirsiniz"; "account.needed.title" = "Hesap gerekli"; "account.option" = "Hesap seçeneği"; +"account.stats.avg.transaction.time.title" = "Avg. transaction time"; +"account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.hold.tokens.usd.title" = "Hold tokens USD"; +"account.stats.max.transaction.time.title" = "Max transaction time"; +"account.stats.min.transactions.time.title" = "Min transaction time"; +"account.stats.native.balance.usd.title" = "Native balance USD"; +"account.stats.rejected.transactions.title" = "Rejected transactions"; +"account.stats.title" = "Your score"; +"account.stats.total.transactions.title" = "Total transactions"; +"account.stats.updated.title" = "Updated"; +"account.stats.wallet.age.title" = "Wallet age"; "account.template" = "%s hesabı"; "account.unique.secret" = "Eşsiz gizli bilgili hesaplar"; "accounts.add.account" = "Hesap ekle"; @@ -477,6 +488,8 @@ Kaynak: %@"; "lp.apy.alert.title" = "Stratejik Bonus APY"; "lp.apy.title" = "Stratejik Bonus APY"; "lp.available.pools.title" = "Mevcut havuzlar"; +"lp.banner.action.details.title" = "Show details"; +"lp.banner.text" = "Invest your funds in Liquidity\npools and receive rewards"; "lp.confirm.liquidity.screen.title" = "Likiditeyi Doğrula"; "lp.confirm.liquidity.warning.text" = "Çıktı tahminidir. Fiyatın %0,5'ten fazla değişmesi durumunda işleminiz geri alınacaktır."; "lp.network.fee.alert.text" = "Ağ ücreti, SORA sisteminin büyümesini ve istikrarlı performansını sağlamak için kullanılır."; @@ -534,6 +547,7 @@ Kaynak: %@"; "nft.creator.title" = "Yaratıcı"; "nft.list.empty.message" = "Henüz NFT yok\ burada görmek için NFT'leri satın alın veya bastırın"; +"nft.load.error" = "Failed to load NFTs"; "nft.owner.title" = "Sahip olunan."; "nft.share.address" = "Alacağım genel adresim:%s"; "nft.spam.warning" = "Beware of a scam/spam NFT collection – verify authenticity before engaging. Stay safe!"; @@ -1185,4 +1199,4 @@ ait olduğundan emin olun."; "your.validators.change.validators.title" = "Doğrulayıcıları değiştir"; "your.validators.stop.nominating.title" = "Aday göstermeyi bırak"; "your.validators.validator.total.stake" = "Toplam yatırılan miktar:%@"; -"сurrencies.stub.text" = "Para birimleri"; +"сurrencies.stub.text" = "Para birimleri"; \ No newline at end of file diff --git a/fearless/vi.lproj/Localizable.strings b/fearless/vi.lproj/Localizable.strings index 13a16f5ee..a3bc1a2a6 100644 --- a/fearless/vi.lproj/Localizable.strings +++ b/fearless/vi.lproj/Localizable.strings @@ -64,6 +64,17 @@ "account.needed.message" = "Bạn không có tài khoản cho mạng (network) này, bạn có thể tạo hoặc nhập tài khoản."; "account.needed.title" = "Tài khoản cần thiết"; "account.option" = "Tùy chọn tài khoản"; +"account.stats.avg.transaction.time.title" = "Avg. transaction time"; +"account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.hold.tokens.usd.title" = "Hold tokens USD"; +"account.stats.max.transaction.time.title" = "Max transaction time"; +"account.stats.min.transactions.time.title" = "Min transaction time"; +"account.stats.native.balance.usd.title" = "Native balance USD"; +"account.stats.rejected.transactions.title" = "Rejected transactions"; +"account.stats.title" = "Your score"; +"account.stats.total.transactions.title" = "Total transactions"; +"account.stats.updated.title" = "Updated"; +"account.stats.wallet.age.title" = "Wallet age"; "account.template" = "tài khoản %s"; "account.unique.secret" = "Tài khoản có khóa bí mật duy nhất"; "accounts.add.account" = "Thêm tài khoản"; @@ -182,7 +193,7 @@ tiền Euro"; "common.action.receive" = "Nhận"; "common.action.send" = "Gửi"; "common.action.teleport" = "Teleport"; -"common.activation.required" = "Activation Required"; +"common.activation.required" = "Yêu cầu kích hoạt"; "common.add" = "Thêm"; "common.address" = "Địa chỉ"; "common.advanced" = "Nâng cao"; @@ -255,7 +266,7 @@ tiền Euro"; "common.message" = "Tin nhắn"; "common.methods" = "Phương pháp"; "common.module" = "Module"; -"common.more" = "More"; +"common.more" = "Thêm"; "common.my.networks" = "Mạng của tôi"; "common.name" = "Tên"; "common.network" = "Mạng"; @@ -404,7 +415,7 @@ tiền Euro"; "error.invalid.address" = "Địa chỉ không hợp lệ cho chuỗi đã chọn"; "error.message.enter.the.name" = "Nhập tên..."; "error.message.enter.the.url.address" = "Nhập địa chỉ URL…"; -"error.scan.qr.disabled.asset" = "The asset you want to transfer is either unsupported or turned off. Go to Asset Management, activate the asset, and then scan the QR code again."; +"error.scan.qr.disabled.asset" = "Nội dung bạn muốn chuyển không được hỗ trợ hoặc bị tắt. Đi tới Quản lý tài sản, kích hoạt tài sản rồi quét lại mã QR."; "error.unsupported.asset" = "Bạn đang cố gắng thực hiện chuyển nhượng nội dung hiện không được hỗ trợ trên Ứng dụng. Vui lòng chọn nội dung khác hoặc yêu cầu mã QR khác."; "ethereum.crypto.type" = "Ethereum keypair crypto type"; "ethereum.secret.derivation.path" = "Ethereum secret derivation path"; @@ -470,26 +481,28 @@ Seed: %@"; "label.testnet" = "Testnet"; "language.title" = "Ngôn ngữ"; "learn.more.about.crowdloans" = "Tìm hiểu thêm về Crowdloans"; -"lp.apy.alert.text" = "Farming reward for liquidity provision"; -"lp.apy.alert.title" = "Strategic Bonus APY"; -"lp.apy.title" = "Strategic Bonus APY"; -"lp.available.pools.title" = "Available pools"; -"lp.confirm.liquidity.screen.title" = "Confirm Liquidity"; -"lp.confirm.liquidity.warning.text" = "Output is estimated. If the price changes more than 0.5% your transaction will revert."; -"lp.network.fee.alert.text" = "Network fee is used to ensure SORA system’s growth and stable performance."; -"lp.network.fee.alert.title" = "Network fee"; -"lp.pool.details.title" = "Pool details"; -"lp.pool.remove.warning.text" = "Removing pool tokens converts your position back into underlying tokens at the current rate, proportional to your share of the pool. Accrued fees are included in the amounts you receive."; -"lp.pool.remove.warning.title" = "NOTE"; -"lp.remove.button.title" = "Remove Liquidity"; -"lp.remove.liquidity.screen.title" = "Remove Liquidity"; -"lp.reward.token.text" = "Earn %@"; -"lp.reward.token.title" = "Rewards Payout In"; -"lp.slippage.title" = "Slippage"; -"lp.supply.button.title" = "Supply Liquidity"; -"lp.supply.liquidity.screen.title" = "Supply Liquidity"; -"lp.token.pooled.text" = "Your %@ Pooled"; -"lp.user.pools.title" = "User pools"; +"lp.apy.alert.text" = "Phần thưởng canh tác để cung cấp thanh khoản"; +"lp.apy.alert.title" = "APY tiền thưởng chiến lược"; +"lp.apy.title" = "APY tiền thưởng chiến lược"; +"lp.available.pools.title" = "Pool có sẵn"; +"lp.banner.action.details.title" = "Hiển thị chi tiết"; +"lp.banner.text" = "Đầu tư tiền của bạn vào Pool\nthanh khoản và nhận phần thưởng"; +"lp.confirm.liquidity.screen.title" = "Xác nhận thanh khoản"; +"lp.confirm.liquidity.warning.text" = "Kết quả được ước tính. Nếu giá thay đổi hơn 0,5% thì giao dịch của bạn sẽ hoàn trả."; +"lp.network.fee.alert.text" = "Phí mạng được sử dụng để đảm bảo sự tăng trưởng và hiệu suất ổn định của hệ thống SORA."; +"lp.network.fee.alert.title" = "Phí mạng"; +"lp.pool.details.title" = "Chi tiết Pool"; +"lp.pool.remove.warning.text" = "Việc xóa pool token sẽ chuyển đổi vị trí của bạn trở lại thành token cơ bản ở mức giá hiện tại, tỷ lệ thuận với phần chia sẻ của bạn trong pool. Phí tích lũy được bao gồm trong số tiền bạn nhận được."; +"lp.pool.remove.warning.title" = "GHI CHÚ"; +"lp.remove.button.title" = "Rút Thanh Khoản"; +"lp.remove.liquidity.screen.title" = "Rút Thanh Khoản"; +"lp.reward.token.text" = "Kiếm được % @"; +"lp.reward.token.title" = "Thanh toán phần thưởng"; +"lp.slippage.title" = "Trượt giá"; +"lp.supply.button.title" = "Nguồnn cung thanh khoản"; +"lp.supply.liquidity.screen.title" = "Nguồnn cung thanh khoản"; +"lp.token.pooled.text" = "%@ của bạn đã đóng góp"; +"lp.user.pools.title" = "Pool người dùng"; "manage.assets.account.missing.text" = "Thêm một tài khoản..."; "manage.assets.search.hint" = "Tìm kiếm theo tài sản"; "members.common" = "Các thành viên"; @@ -535,6 +548,7 @@ Xin lưu ý rằng việc ký giao dịch này sẽ phải chịu phí giao dị "nft.collection.title" = "Bộ sưu tập"; "nft.creator.title" = "Người sáng tạo"; "nft.list.empty.message" = "Chưa có bất kỳ NFT nào. Mua hoặc đúc NFT để xem chúng tại đây."; +"nft.load.error" = "Failed to load NFTs"; "nft.owner.title" = "Sở hữu"; "nft.share.address" = "Địa chỉ công khai của tôi để nhận: %s"; "nft.spam.warning" = "Cảnh giác với hoạt động thu thập NFT lừa đảo/spam – hãy xác minh tính xác thực trước khi tham gia. Giữ an toàn!"; @@ -1187,4 +1201,4 @@ thuộc đúng mạng"; "your.validators.change.validators.title" = "Thay đổi validator"; "your.validators.stop.nominating.title" = "Dừng đề cử"; "your.validators.validator.total.stake" = "Tổng đã stake: %@"; -"сurrencies.stub.text" = "Tiền tệ"; +"сurrencies.stub.text" = "Tiền tệ"; \ No newline at end of file diff --git a/fearless/zh-Hans.lproj/Localizable.strings b/fearless/zh-Hans.lproj/Localizable.strings index 67f9f11f1..19e7a54aa 100644 --- a/fearless/zh-Hans.lproj/Localizable.strings +++ b/fearless/zh-Hans.lproj/Localizable.strings @@ -64,6 +64,17 @@ "account.needed.message" = "您还没有此网络的账户,您可以创建或导入一个账户。"; "account.needed.title" = "所需账户"; "account.option" = "账户选项"; +"account.stats.avg.transaction.time.title" = "Avg. transaction time"; +"account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.hold.tokens.usd.title" = "Hold tokens USD"; +"account.stats.max.transaction.time.title" = "Max transaction time"; +"account.stats.min.transactions.time.title" = "Min transaction time"; +"account.stats.native.balance.usd.title" = "Native balance USD"; +"account.stats.rejected.transactions.title" = "Rejected transactions"; +"account.stats.title" = "Your score"; +"account.stats.total.transactions.title" = "Total transactions"; +"account.stats.updated.title" = "Updated"; +"account.stats.wallet.age.title" = "Wallet age"; "account.template" = "%s账户"; "account.unique.secret" = "拥有独特密钥的账户"; "accounts.add.account" = "添加一个账户"; @@ -474,6 +485,8 @@ "lp.apy.alert.title" = "战略奖励APY"; "lp.apy.title" = "战略奖励APY"; "lp.available.pools.title" = "可用的流动池"; +"lp.banner.action.details.title" = "Show details"; +"lp.banner.text" = "Invest your funds in Liquidity\npools and receive rewards"; "lp.confirm.liquidity.screen.title" = "确认流动性"; "lp.confirm.liquidity.warning.text" = "输出是估计的。如果价格变动超过0.5%,您的交易将会回滚。"; "lp.network.fee.alert.text" = "网络费用用于确保 SORA 系统的增长和稳定性能。"; @@ -535,6 +548,7 @@ "nft.collection.title" = "收藏品"; "nft.creator.title" = "创作者"; "nft.list.empty.message" = "目前还没有任何NFT。购买或铸造NFT以在此处查看。"; +"nft.load.error" = "Failed to load NFTs"; "nft.owner.title" = "拥有"; "nft.share.address" = "我要接收的公共地址:%s"; "nft.spam.warning" = "小心某些 NFT 收藏品可能是骗局或垃圾邮件,在参与之前请先验证其真实性。保持安全!"; @@ -1184,4 +1198,4 @@ "your.validators.change.validators.title" = "更改验证人"; "your.validators.stop.nominating.title" = "停止提名"; "your.validators.validator.total.stake" = "总质押:%@"; -"сurrencies.stub.text" = "货币"; +"сurrencies.stub.text" = "货币"; \ No newline at end of file diff --git a/fearlessTests/Modules/AccountStatistics/AccountStatisticsTests.swift b/fearlessTests/Modules/AccountStatistics/AccountStatisticsTests.swift new file mode 100644 index 000000000..67073583f --- /dev/null +++ b/fearlessTests/Modules/AccountStatistics/AccountStatisticsTests.swift @@ -0,0 +1,16 @@ +import XCTest + +class AccountStatisticsTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + XCTFail("Did you forget to add tests?") + } +} From cdd5f7179e63de9e84cc05efce31493c7cd2c658 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Fri, 19 Jul 2024 11:15:03 +0700 Subject: [PATCH 04/18] account score view with data --- fearless.xcodeproj/project.pbxproj | 28 ++++++++++ .../View/AccountScore/AccountScoreView.swift | 53 +++++++++++++++++++ .../AccountScore/AccountScoreViewModel.swift | 33 ++++++++++++ .../AccountScoreViewModelFactory.swift | 17 ++++++ .../WalletMainContainerAssembly.swift | 3 +- .../WalletMainContainerInteractor.swift | 6 ++- .../WalletMainContainerPresenter.swift | 13 ++++- .../WalletMainContainerProtocols.swift | 3 ++ .../WalletMainContainerViewController.swift | 4 ++ .../WalletMainContainerViewLayout.swift | 12 +++++ 10 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 fearless/Common/View/AccountScore/AccountScoreView.swift create mode 100644 fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift create mode 100644 fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift diff --git a/fearless.xcodeproj/project.pbxproj b/fearless.xcodeproj/project.pbxproj index 9f7807bc4..07e3cdfb6 100644 --- a/fearless.xcodeproj/project.pbxproj +++ b/fearless.xcodeproj/project.pbxproj @@ -3034,6 +3034,9 @@ FAF5E9DE27E46DCC005A3448 /* String+VersionComparsion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF5E9DD27E46DCC005A3448 /* String+VersionComparsion.swift */; }; FAF5E9E127E4A4C1005A3448 /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF5E9E027E4A4C1005A3448 /* RootViewModel.swift */; }; FAF600752C48D79600E56558 /* Cosmos in Frameworks */ = {isa = PBXBuildFile; productRef = FAF600742C48D79600E56558 /* Cosmos */; }; + FAF600772C48F08B00E56558 /* AccountScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF600762C48F08B00E56558 /* AccountScoreView.swift */; }; + FAF6007A2C48F12000E56558 /* AccountScoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */; }; + FAF6007C2C48FC2500E56558 /* AccountScoreViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */; }; FAF92E6627B4275F005467CE /* Bool+ToInt.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF92E6527B4275E005467CE /* Bool+ToInt.swift */; }; FAF96B582B636FC700E299C1 /* SystemNumberRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF96B572B636FC700E299C1 /* SystemNumberRequest.swift */; }; FAF9C2962AAADE3300A61D21 /* DeprecatedControllerStashAccountCheckService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF9C2952AAADE3300A61D21 /* DeprecatedControllerStashAccountCheckService.swift */; }; @@ -6162,6 +6165,9 @@ FAF5E9DA27E46DAA005A3448 /* RootViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = ""; }; FAF5E9DD27E46DCC005A3448 /* String+VersionComparsion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+VersionComparsion.swift"; sourceTree = ""; }; FAF5E9E027E4A4C1005A3448 /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = ""; }; + FAF600762C48F08B00E56558 /* AccountScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreView.swift; sourceTree = ""; }; + FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreViewModel.swift; sourceTree = ""; }; + FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreViewModelFactory.swift; sourceTree = ""; }; FAF92E6527B4275E005467CE /* Bool+ToInt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bool+ToInt.swift"; sourceTree = ""; }; FAF96B572B636FC700E299C1 /* SystemNumberRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemNumberRequest.swift; sourceTree = ""; }; FAF9C2952AAADE3300A61D21 /* DeprecatedControllerStashAccountCheckService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeprecatedControllerStashAccountCheckService.swift; sourceTree = ""; }; @@ -9864,6 +9870,7 @@ FA2222932BD2726F0031DE04 /* SkeletonLabel.swift */, FA2222952BD272A30031DE04 /* SkeletonLoadableView.swift */, FA887A482C1C19DB00CA720F /* WarningView.swift */, + FAF600782C48F11200E56558 /* AccountScore */, ); path = View; sourceTree = ""; @@ -10046,6 +10053,7 @@ 849244902514EDD900477C1B /* ViewModel */ = { isa = PBXGroup; children = ( + FAF6007D2C48FC2A00E56558 /* AccountScore */, FA1D02052BBE71F9005B7071 /* TokenPairsIconViewModel.swift */, 0702B3162970182B003519F5 /* Amount */, FAA013A028DA1328000A5230 /* StakeAmountViewModel.swift */, @@ -15551,6 +15559,23 @@ path = ViewModel; sourceTree = ""; }; + FAF600782C48F11200E56558 /* AccountScore */ = { + isa = PBXGroup; + children = ( + FAF600762C48F08B00E56558 /* AccountScoreView.swift */, + ); + path = AccountScore; + sourceTree = ""; + }; + FAF6007D2C48FC2A00E56558 /* AccountScore */ = { + isa = PBXGroup; + children = ( + FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */, + FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */, + ); + path = AccountScore; + sourceTree = ""; + }; FAF96B562B636FBC00E299C1 /* SystemPallet */ = { isa = PBXGroup; children = ( @@ -16892,6 +16917,7 @@ 8490147624A94A37008F705E /* RootInteractor.swift in Sources */, F40966CE26B297D6008CD244 /* AnalyticsStakeProtocols.swift in Sources */, 07DE95B728A1119400E9C2CB /* BalanceInfoProtocols.swift in Sources */, + FAF6007A2C48F12000E56558 /* AccountScoreViewModel.swift in Sources */, 849014C524AA890D008F705E /* UIFont+Style.swift in Sources */, 0726FFB12AC439DE00336D76 /* WalletConnectPolkadotSignature.swift in Sources */, FA3430EE285065A1002B5975 /* StakingUnbondConfirmRelaychainStrategy.swift in Sources */, @@ -18742,6 +18768,7 @@ 4F66A13E327CBDD04A2411FA /* WalletTransactionHistoryViewLayout.swift in Sources */, FA8800612B31A027000AE5EB /* StakingAccountResolverV14.swift in Sources */, D41CA684433AD861BEC2213B /* WalletTransactionHistoryViewFactory.swift in Sources */, + FAF600772C48F08B00E56558 /* AccountScoreView.swift in Sources */, C77CCA7FF969A2F006A0B6C4 /* WalletChainAccountDashboardProtocols.swift in Sources */, 07DE95C728A169A600E9C2CB /* AssetListSearchProtocols.swift in Sources */, FA99549B27B3B60700CCC94B /* WalletNameChanged.swift in Sources */, @@ -18903,6 +18930,7 @@ B15D513381B7626AB90018F0 /* StakingPoolInfoInteractor.swift in Sources */, 89C8A9B990B08016A70ED336 /* StakingPoolInfoViewController.swift in Sources */, 281D17EF8C45A1FC49FD1523 /* StakingPoolInfoViewLayout.swift in Sources */, + FAF6007C2C48FC2500E56558 /* AccountScoreViewModelFactory.swift in Sources */, 39DA5795FB9DBF626B72B5C6 /* StakingPoolInfoAssembly.swift in Sources */, 4A63ECA587C601999AAEB974 /* StakingPoolCreateProtocols.swift in Sources */, 7BC6FB48B9B4EC790923FF1E /* StakingPoolCreateRouter.swift in Sources */, diff --git a/fearless/Common/View/AccountScore/AccountScoreView.swift b/fearless/Common/View/AccountScore/AccountScoreView.swift new file mode 100644 index 000000000..021a98e3b --- /dev/null +++ b/fearless/Common/View/AccountScore/AccountScoreView.swift @@ -0,0 +1,53 @@ +import UIKit +import Cosmos + +class AccountScoreView: UIView { + let starView: CosmosView = { + let view = CosmosView() + view.settings.totalStars = 1 + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + addSubviews() + setupConstraints() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func bind(viewModel: AccountScoreViewModel) { + starView.text = viewModel.accountScoreLabelText + + starView.settings.textFont = .h5Title + if let color = viewModel.rate.color { + starView.settings.emptyBorderColor = color + starView.settings.filledColor = color + starView.settings.filledBorderColor = color + starView.settings.textColor = color + } + + switch viewModel.rate { + case .high: + starView.settings.fillMode = .full + case .medium: + starView.settings.fillMode = .half + case .low: + starView.settings.fillMode = .precise + } + } + + private func addSubviews() { + addSubview(starView) + } + + private func setupConstraints() { + starView.snp.makeConstraints { make in + make.edges.equalToSuperview() + make.height.equalTo(15) + } + } +} diff --git a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift new file mode 100644 index 000000000..254396ef9 --- /dev/null +++ b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift @@ -0,0 +1,33 @@ +import UIKit + +enum AccountScoreRate { + case low + case medium + case high + + init(from score: Decimal) { + if score < 0.25 { + self = .low + } else if score < 0.75 { + self = .medium + } else { + self = .high + } + } + + var color: UIColor? { + switch self { + case .low: + return R.color.colorRed() + case .medium: + return R.color.colorOrange() + case .high: + return R.color.colorGreen() + } + } +} + +struct AccountScoreViewModel { + let accountScoreLabelText: String + let rate: AccountScoreRate +} diff --git a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift new file mode 100644 index 000000000..a48b484e7 --- /dev/null +++ b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift @@ -0,0 +1,17 @@ +import Foundation + +protocol AccountScoreViewModelFactory { + func buildViewModel(from accountStatistics: AccountStatistics) -> AccountScoreViewModel? +} + +final class AccountScoreViewModelFactoryImpl: AccountScoreViewModelFactory { + func buildViewModel(from accountStatistics: AccountStatistics) -> AccountScoreViewModel? { + guard let score = accountStatistics.score else { + return nil + } + + let rate = AccountScoreRate(from: score) + let intScore = ((score * 100.0) as NSDecimalNumber).intValue + return AccountScoreViewModel(accountScoreLabelText: "\(intScore)", rate: rate) + } +} diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift index 13387c1ad..425f6ba72 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift @@ -81,7 +81,8 @@ final class WalletMainContainerAssembly { viewModelFactory: WalletMainContainerViewModelFactory(), interactor: interactor, router: router, - localizationManager: localizationManager + localizationManager: localizationManager, + accountScoreViewModelFactory: AccountScoreViewModelFactoryImpl() ) let view = WalletMainContainerViewController( diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift index 9fc87b498..8a76f6b56 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift @@ -56,7 +56,11 @@ final class WalletMainContainerInteractor { do { let stream = try await accountStatisticsFetcher.subscribeForStatistics(address: address, cacheOptions: .onAll) for try await statistics in stream { - print("Account statistics: ", statistics.value?.data) + if let stats = statistics.value?.data { + DispatchQueue.main.async { [weak self] in + self?.output?.didReceiveAccountStatistics(stats) + } + } } } catch { print("Account statistics fetching error: ", error) diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift index 16829735a..ad255f1f6 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift @@ -13,6 +13,7 @@ final class WalletMainContainerPresenter { private weak var view: WalletMainContainerViewInput? private let router: WalletMainContainerRouterInput private let interactor: WalletMainContainerInteractorInput + private let accountScoreViewModelFactory: AccountScoreViewModelFactory private var wallet: MetaAccountModel private let viewModelFactory: WalletMainContainerViewModelFactoryProtocol @@ -32,7 +33,8 @@ final class WalletMainContainerPresenter { viewModelFactory: WalletMainContainerViewModelFactoryProtocol, interactor: WalletMainContainerInteractorInput, router: WalletMainContainerRouterInput, - localizationManager: LocalizationManagerProtocol + localizationManager: LocalizationManagerProtocol, + accountScoreViewModelFactory: AccountScoreViewModelFactory ) { self.balanceInfoModuleInput = balanceInfoModuleInput self.assetListModuleInput = assetListModuleInput @@ -41,6 +43,7 @@ final class WalletMainContainerPresenter { self.viewModelFactory = viewModelFactory self.interactor = interactor self.router = router + self.accountScoreViewModelFactory = accountScoreViewModelFactory self.localizationManager = localizationManager } @@ -218,6 +221,14 @@ extension WalletMainContainerPresenter: WalletMainContainerInteractorOutput { actions: [action] ) } + + func didReceiveAccountStatistics(_ accountStatistics: AccountStatistics) { + if let viewModel = accountScoreViewModelFactory.buildViewModel(from: accountStatistics) { + view?.didReceiveAccountScoreViewModel(viewModel) + } + } + + func didReceiveAccountStatisticsError(_: Error) {} } // MARK: - Localizable diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift index f28b101e0..8f9a15e2f 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift @@ -7,6 +7,7 @@ typealias WalletMainContainerModuleCreationResult = ( protocol WalletMainContainerViewInput: ControllerBackedProtocol, HiddableBarWhenPushed { func didReceiveViewModel(_ viewModel: WalletMainContainerViewModel) + func didReceiveAccountScoreViewModel(_ viewModel: AccountScoreViewModel) } protocol WalletMainContainerViewOutput: AnyObject { @@ -30,6 +31,8 @@ protocol WalletMainContainerInteractorOutput: AnyObject { func didReceiveError(_ error: Error) func didReceiveControllerAccountIssue(issue: ControllerAccountIssue, hasStashItem: Bool) func didReceiveStashAccountIssue(address: String) + func didReceiveAccountStatistics(_ accountStatistics: AccountStatistics) + func didReceiveAccountStatisticsError(_ error: Error) } protocol WalletMainContainerRouterInput: SheetAlertPresentable, ErrorPresentable, ApplicationStatusPresentable, AccountManagementPresentable { diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift index df59a758d..20544fd27 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift @@ -111,6 +111,10 @@ extension WalletMainContainerViewController: WalletMainContainerViewInput { func didReceiveViewModel(_ viewModel: WalletMainContainerViewModel) { rootView.bind(viewModel: viewModel) } + + func didReceiveAccountScoreViewModel(_ viewModel: AccountScoreViewModel) { + rootView.bind(accountScoreViewModel: viewModel) + } } // MARK: - Localizable diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift index aaa1430da..495660235 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift @@ -36,6 +36,8 @@ final class WalletMainContainerViewLayout: UIView { return button }() + let accountScoreView = AccountScoreView() + private let walletNameTitle: UILabel = { let label = UILabel() label.font = .h4Title @@ -120,6 +122,10 @@ final class WalletMainContainerViewLayout: UIView { } } + func bind(accountScoreViewModel: AccountScoreViewModel) { + accountScoreView.bind(viewModel: accountScoreViewModel) + } + // MARK: - Private methods private func applyLocalization() { @@ -158,6 +164,12 @@ final class WalletMainContainerViewLayout: UIView { make.size.equalTo(Constants.walletIconSize) } + navigationContainerView.addSubview(accountScoreView) + accountScoreView.snp.makeConstraints { make in + make.top.equalTo(switchWalletButton.snp.bottom).offset(4) + make.centerX.equalTo(switchWalletButton.snp.centerX) + } + let walletInfoVStackView = UIFactory.default.createVerticalStackView(spacing: 6) walletInfoVStackView.alignment = .center walletInfoVStackView.distribution = .fill From 1a1f6875b4b2f8c4731dcf27e5e681115ad3015a Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Fri, 19 Jul 2024 16:20:37 +0700 Subject: [PATCH 05/18] fixes --- .../FeatureToggle/FeatureToggleConfig.swift | 3 ++- .../Common/Configs/ApplicationConfigs.swift | 6 ++++- ...AssetTransactionData+SubqueryHistory.swift | 24 ++++++++++++------- .../Subquery/Data/SubqueryTransfer.swift | 2 +- .../WalletMainContainerAssembly.swift | 9 ++++++- .../WalletMainContainerInteractor.swift | 20 +++++++++++++++- .../WalletMainContainerPresenter.swift | 4 ++++ .../WalletMainContainerProtocols.swift | 2 ++ .../WalletMainContainerViewController.swift | 4 ++++ .../WalletMainContainerViewLayout.swift | 4 +++- fearless/en.lproj/InfoPlist.strings | 2 +- fearless/ru.lproj/InfoPlist.strings | 2 +- 12 files changed, 65 insertions(+), 17 deletions(-) diff --git a/fearless/ApplicationLayer/Services/FeatureToggle/FeatureToggleConfig.swift b/fearless/ApplicationLayer/Services/FeatureToggle/FeatureToggleConfig.swift index 25e9465d6..c2732eed3 100644 --- a/fearless/ApplicationLayer/Services/FeatureToggle/FeatureToggleConfig.swift +++ b/fearless/ApplicationLayer/Services/FeatureToggle/FeatureToggleConfig.swift @@ -2,8 +2,9 @@ import Foundation struct FeatureToggleConfig: Decodable { let pendulumCaseEnabled: Bool? + let nftEnabled: Bool? static var defaultConfig: FeatureToggleConfig { - FeatureToggleConfig(pendulumCaseEnabled: false) + FeatureToggleConfig(pendulumCaseEnabled: false, nftEnabled: true) } } diff --git a/fearless/Common/Configs/ApplicationConfigs.swift b/fearless/Common/Configs/ApplicationConfigs.swift index bdd094313..1762211e4 100644 --- a/fearless/Common/Configs/ApplicationConfigs.swift +++ b/fearless/Common/Configs/ApplicationConfigs.swift @@ -215,7 +215,11 @@ extension ApplicationConfig: ApplicationConfigProtocol, XcmConfigProtocol { } var onboardingConfig: URL? { - GitHubUrl.url(suffix: "appConfigs/onboarding/mobile v2.json", branch: .developFree) + #if F_DEV + GitHubUrl.url(suffix: "appConfigs/onboarding/mobile v2.json", branch: .developFree) + #else + GitHubUrl.url(suffix: "appConfigs/onboarding/mobile v2.json") + #endif } var soraSubqueryUrl: URL { diff --git a/fearless/Common/Extension/Wallet/AssetTransactionData+SubqueryHistory.swift b/fearless/Common/Extension/Wallet/AssetTransactionData+SubqueryHistory.swift index 58794f9b7..08572fd27 100644 --- a/fearless/Common/Extension/Wallet/AssetTransactionData+SubqueryHistory.swift +++ b/fearless/Common/Extension/Wallet/AssetTransactionData+SubqueryHistory.swift @@ -82,15 +82,21 @@ extension AssetTransactionData { BigUInt(string: transfer.amount) ?? 0, precision: Int16(asset.precision) ) ?? .zero - let feeValue = BigUInt(string: transfer.fee) ?? BigUInt(0) - let feeDecimal = Decimal.fromSubstrateAmount(feeValue, precision: Int16(asset.precision)) ?? .zero - let fee = AssetTransactionFee( - identifier: asset.id, - assetId: asset.id, - amount: AmountDecimal(value: feeDecimal), - context: nil - ) + var fees: [AssetTransactionFee] = [] + + if let fee = transfer.fee { + let feeValue = BigUInt(string: fee) ?? BigUInt(0) + let feeDecimal = Decimal.fromSubstrateAmount(feeValue, precision: Int16(asset.precision)) ?? .zero + + let fee = AssetTransactionFee( + identifier: asset.id, + assetId: asset.id, + amount: AmountDecimal(value: feeDecimal), + context: nil + ) + fees.append(fee) + } let type = transfer.sender == address ? TransactionType.outgoing : TransactionType.incoming @@ -107,7 +113,7 @@ extension AssetTransactionData { peerName: peerAddress, details: "", amount: AmountDecimal(value: amount), - fees: [fee], + fees: fees, timestamp: timestamp, type: type.rawValue, reason: "", diff --git a/fearless/Common/Network/BlockExplorer/Subquery/Data/SubqueryTransfer.swift b/fearless/Common/Network/BlockExplorer/Subquery/Data/SubqueryTransfer.swift index 4c59e0085..9d276bb9a 100644 --- a/fearless/Common/Network/BlockExplorer/Subquery/Data/SubqueryTransfer.swift +++ b/fearless/Common/Network/BlockExplorer/Subquery/Data/SubqueryTransfer.swift @@ -16,7 +16,7 @@ struct SubqueryTransfer: Decodable { let amount: String let receiver: String let sender: String - let fee: String + let fee: String? let block: String? let extrinsicId: String? let extrinsicHash: String? diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift index e6e0ee09f..0e123822a 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift @@ -2,6 +2,7 @@ import UIKit import SoraFoundation import RobinHood import SSFUtils +import SSFNetwork final class WalletMainContainerAssembly { static func configureModule( @@ -50,6 +51,11 @@ final class WalletMainContainerAssembly { stashItemRepository: substrateRepositoryFactory.createStashItemRepository() ) + let featureToggleProvider = FeatureToggleProvider( + networkOperationFactory: NetworkOperationFactory(jsonDecoder: GithubJSONDecoder()), + operationQueue: OperationQueue() + ) + let interactor = WalletMainContainerInteractor( accountRepository: AnyDataProviderRepository(accountRepository), chainRepository: AnyDataProviderRepository(chainRepository), @@ -58,7 +64,8 @@ final class WalletMainContainerAssembly { eventCenter: EventCenter.shared, deprecatedAccountsCheckService: deprecatedAccountsCheckService, applicationHandler: ApplicationHandler(), - walletConnectService: walletConnect + walletConnectService: walletConnect, + featureToggleService: featureToggleProvider ) let router = WalletMainContainerRouter() diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift index b60df973b..ced7322b6 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift @@ -16,6 +16,7 @@ final class WalletMainContainerInteractor { private let deprecatedAccountsCheckService: DeprecatedControllerStashAccountCheckServiceProtocol private let applicationHandler: ApplicationHandler private let walletConnectService: WalletConnectService + private let featureToggleService: FeatureToggleProviderProtocol // MARK: - Constructor @@ -27,7 +28,8 @@ final class WalletMainContainerInteractor { eventCenter: EventCenterProtocol, deprecatedAccountsCheckService: DeprecatedControllerStashAccountCheckServiceProtocol, applicationHandler: ApplicationHandler, - walletConnectService: WalletConnectService + walletConnectService: WalletConnectService, + featureToggleService: FeatureToggleProviderProtocol ) { self.wallet = wallet self.chainRepository = chainRepository @@ -37,6 +39,7 @@ final class WalletMainContainerInteractor { self.deprecatedAccountsCheckService = deprecatedAccountsCheckService self.applicationHandler = applicationHandler self.walletConnectService = walletConnectService + self.featureToggleService = featureToggleService applicationHandler.delegate = self } @@ -99,6 +102,20 @@ final class WalletMainContainerInteractor { } } } + + private func checkNftAvailability() { + let fetchOperation = featureToggleService.fetchConfigOperation() + + fetchOperation.completionBlock = { + let config = try? fetchOperation.extractNoCancellableResultData() + + DispatchQueue.main.async { [weak self] in + self?.output?.didReceiveNftAvailability(isNftAvailable: config?.nftEnabled == true) + } + } + + operationQueue.addOperation(fetchOperation) + } } // MARK: - WalletMainContainerInteractorInput @@ -108,6 +125,7 @@ extension WalletMainContainerInteractor: WalletMainContainerInteractorInput { self.output = output eventCenter.add(observer: self, dispatchIn: .main) fetchNetworkManagmentFilter() + checkNftAvailability() } func walletConnect(uri: String) async throws { diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift index 16829735a..905a6379b 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift @@ -218,6 +218,10 @@ extension WalletMainContainerPresenter: WalletMainContainerInteractorOutput { actions: [action] ) } + + func didReceiveNftAvailability(isNftAvailable: Bool) { + view?.didReceiveNftAvailability(isNftAvailable: isNftAvailable) + } } // MARK: - Localizable diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift index f28b101e0..979d20a56 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift @@ -7,6 +7,7 @@ typealias WalletMainContainerModuleCreationResult = ( protocol WalletMainContainerViewInput: ControllerBackedProtocol, HiddableBarWhenPushed { func didReceiveViewModel(_ viewModel: WalletMainContainerViewModel) + func didReceiveNftAvailability(isNftAvailable: Bool) } protocol WalletMainContainerViewOutput: AnyObject { @@ -30,6 +31,7 @@ protocol WalletMainContainerInteractorOutput: AnyObject { func didReceiveError(_ error: Error) func didReceiveControllerAccountIssue(issue: ControllerAccountIssue, hasStashItem: Bool) func didReceiveStashAccountIssue(address: String) + func didReceiveNftAvailability(isNftAvailable: Bool) } protocol WalletMainContainerRouterInput: SheetAlertPresentable, ErrorPresentable, ApplicationStatusPresentable, AccountManagementPresentable { diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift index df59a758d..8b597e737 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift @@ -111,6 +111,10 @@ extension WalletMainContainerViewController: WalletMainContainerViewInput { func didReceiveViewModel(_ viewModel: WalletMainContainerViewModel) { rootView.bind(viewModel: viewModel) } + + func didReceiveNftAvailability(isNftAvailable: Bool) { + rootView.segmentContainer.isHidden = !isNftAvailable + } } // MARK: - Localizable diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift index aaa1430da..b3d0f72b9 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift @@ -74,6 +74,7 @@ final class WalletMainContainerViewLayout: UIView { // MARK: - FWSegmentedControl + let segmentContainer = UIView() let segmentedControl = FWSegmentedControl() // MARK: - UIPageViewController @@ -148,6 +149,8 @@ final class WalletMainContainerViewLayout: UIView { setupWalletBalanceLayout() setupSegmentedLayout() setupListLayout() + + segmentContainer.isHidden = true } private func setupNavigationViewLayout() { @@ -221,7 +224,6 @@ final class WalletMainContainerViewLayout: UIView { private func setupSegmentedLayout() { contentView.setCustomSpacing(32, after: walletBalanceVStackView) - let segmentContainer = UIView() contentView.addArrangedSubview(segmentContainer) segmentContainer.addSubview(segmentedControl) segmentedControl.snp.makeConstraints { make in diff --git a/fearless/en.lproj/InfoPlist.strings b/fearless/en.lproj/InfoPlist.strings index 3a51606cb..ee22f86b5 100644 --- a/fearless/en.lproj/InfoPlist.strings +++ b/fearless/en.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ "NSCameraUsageDescription" = "Camera is used to capture QR code"; "NSFaceIDUsageDescription" = "Fearless uses Face ID to restrict unauthorized users from accessing the app."; -"NSPhotoLibraryUsageDescription" = "Load photos from library"; +"NSPhotoLibraryUsageDescription" = "Access to the library is needed to select existing images of QR codes"; "NSPhotoLibraryAddUsageDescription" = "Save transfer request as a qr code"; diff --git a/fearless/ru.lproj/InfoPlist.strings b/fearless/ru.lproj/InfoPlist.strings index c30e56779..edd5092ea 100644 --- a/fearless/ru.lproj/InfoPlist.strings +++ b/fearless/ru.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ "NSCameraUsageDescription" = "Камера используется для сканирования QR кода."; "NSFaceIDUsageDescription" = "Fearless использует Face ID, чтобы ограничить несанкционированных пользователей доступа к приложению."; -"NSPhotoLibraryUsageDescription" = "Загрузить изображения из библиотеки"; +"NSPhotoLibraryUsageDescription" = "Для выбора существующих изображений QR-кодов необходим доступ к библиотеке"; "NSPhotoLibraryAddUsageDescription" = "Вы можете сохранить запрос на перевод в виде QR кода"; From 064c1509fd4df67775a57067d2235a23c0d8c63e Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Wed, 24 Jul 2024 15:54:38 +0700 Subject: [PATCH 06/18] accout score ready --- fearless.xcodeproj/project.pbxproj | 28 ++++ .../AccountStatisticsFetching.swift | 2 + .../Nomis/NomisAccountStatisticsFetcher.swift | 17 ++- .../Nomis/NomisJSONDecoder.swift | 10 ++ .../ScamService/ScamInfoFetcher.swift | 61 +++++++++ fearless/Common/Model/AccountStatistics.swift | 28 ++++ fearless/Common/Model/ScamInfo.swift | 3 + .../View/AccountScore/AccountScoreView.swift | 29 +++-- .../View/ScamWarningExpandableView.swift | 7 +- .../Common/View/SkeletonLoadableView.swift | 19 ++- .../Common/View/TitleCopyableValueView.swift | 4 + fearless/Common/View/TitleValueView.swift | 12 +- .../AccountScore/AccountScoreViewModel.swift | 41 +++++- .../AccountScoreViewModelFactory.swift | 34 ++--- .../AccountStatisticsAssembly.swift | 9 +- .../AccountStatisticsInteractor.swift | 26 +++- .../AccountStatisticsPresentable.swift | 27 ++++ .../AccountStatisticsPresenter.swift | 47 ++++++- .../AccountStatisticsProtocols.swift | 2 +- .../AccountStatisticsViewController.swift | 21 ++- .../AccountStatisticsViewLayout.swift | 123 +++++++++++++++--- .../AccountStatisticsViewModel.swift | 17 +++ .../AccountStatisticsViewModelFactory.swift | 58 +++++++++ .../BackupWallet/BackupWalletAssembly.swift | 9 +- .../BackupWallet/BackupWalletPresenter.swift | 9 +- .../BackupWalletViewModelFactory.swift | 11 +- .../Contacts/Cell/ContactTableCell.swift | 31 ++++- .../Modules/Contacts/ContactsAssembly.swift | 4 +- .../Modules/Contacts/ContactsPresenter.swift | 4 + .../Modules/Contacts/ContactsProtocols.swift | 2 +- .../AddressBookViewModelFactory.swift | 18 ++- .../View Models/ContactsTableCellModel.swift | 9 +- .../Modules/NFT/NftSend/NftSendAssembly.swift | 6 +- .../NFT/NftSend/NftSendInteractor.swift | 25 +--- .../Modules/Profile/ProfilePresenter.swift | 4 + .../Modules/Profile/ProfileProtocol.swift | 3 +- .../Profile/ProfileViewController.swift | 4 + .../Modules/Profile/ProfileViewFactory.swift | 9 +- .../ViewModel/ProfileViewModelFactory.swift | 11 +- fearless/Modules/Send/SendAssembly.swift | 4 +- fearless/Modules/Send/SendInteractor.swift | 25 +--- fearless/Modules/Send/SendPresenter.swift | 5 +- .../WalletConnectSessionAssembly.swift | 6 +- ...WalletConnectSessionViewModelFactory.swift | 14 +- .../WalletMainContainerViewModel.swift | 1 + .../WalletMainContainerViewModelFactory.swift | 12 +- .../WalletMainContainerAssembly.swift | 15 ++- .../WalletMainContainerInteractor.swift | 31 +---- .../WalletMainContainerPresenter.swift | 21 ++- .../WalletMainContainerProtocols.swift | 6 +- .../WalletMainContainerViewController.swift | 5 + .../WalletMainContainerViewLayout.swift | 14 +- .../WalletOption/WalletOptionPresenter.swift | 8 ++ .../WalletOption/WalletOptionProtocols.swift | 3 +- .../WalletOptionViewController.swift | 3 + .../WalletOption/WalletOptionViewLayout.swift | 17 ++- .../WalletsManagmentCellViewModel.swift | 1 + .../WalletsManagmentViewModelFactory.swift | 19 ++- .../WalletsManagmentAssembly.swift | 14 +- .../WalletsManagmentPresenter.swift | 4 + .../WalletsManagmentProtocols.swift | 3 +- .../WalletsManagmentTableCell.swift | 18 +++ .../WalletsManagmentViewController.swift | 4 + fearless/en.lproj/Localizable.strings | 3 +- fearless/id.lproj/Localizable.strings | 3 +- fearless/ja.lproj/Localizable.strings | 3 +- fearless/pt-PT.lproj/Localizable.strings | 1 + fearless/ru.lproj/Localizable.strings | 3 +- fearless/tr.lproj/Localizable.strings | 3 +- fearless/vi.lproj/Localizable.strings | 3 +- fearless/zh-Hans.lproj/Localizable.strings | 3 +- 71 files changed, 843 insertions(+), 216 deletions(-) create mode 100644 fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisJSONDecoder.swift create mode 100644 fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift create mode 100644 fearless/Modules/AccountStatistics/AccountStatisticsPresentable.swift create mode 100644 fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModel.swift create mode 100644 fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModelFactory.swift diff --git a/fearless.xcodeproj/project.pbxproj b/fearless.xcodeproj/project.pbxproj index 07e3cdfb6..ba5e3b21a 100644 --- a/fearless.xcodeproj/project.pbxproj +++ b/fearless.xcodeproj/project.pbxproj @@ -1975,6 +1975,7 @@ FA2222912BD239500031DE04 /* SoraSubqueryPriceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA2222902BD239500031DE04 /* SoraSubqueryPriceResponse.swift */; }; FA2222942BD2726F0031DE04 /* SkeletonLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA2222932BD2726F0031DE04 /* SkeletonLabel.swift */; }; FA2222962BD272A30031DE04 /* SkeletonLoadableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA2222952BD272A30031DE04 /* SkeletonLoadableView.swift */; }; + FA236A412C4FA0A4009330F2 /* NomisJSONDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA236A402C4FA0A4009330F2 /* NomisJSONDecoder.swift */; }; FA24FEFE2B95C32200CD9E04 /* Decimal+DoubleValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA24FEFD2B95C32200CD9E04 /* Decimal+DoubleValue.swift */; }; FA256984274CE5A500875A53 /* BalanceLockType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA256982274CE5A400875A53 /* BalanceLockType.swift */; }; FA256985274CE5A500875A53 /* BalanceLocks+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA256983274CE5A500875A53 /* BalanceLocks+Sort.swift */; }; @@ -2025,6 +2026,8 @@ FA256A45274CE8BD00875A53 /* StoriesCollectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA256A43274CE8BC00875A53 /* StoriesCollectionItem.swift */; }; FA256A46274CE8BD00875A53 /* StoriesCollectionItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA256A44274CE8BD00875A53 /* StoriesCollectionItem.xib */; }; FA256A48274CE8C200875A53 /* StakingMainInteractor+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA256A47274CE8C200875A53 /* StakingMainInteractor+Subscription.swift */; }; + FA273E5C2C4F67AA00F9CB13 /* AccountStatisticsViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA273E5B2C4F67A900F9CB13 /* AccountStatisticsViewModelFactory.swift */; }; + FA273E5E2C4F680500F9CB13 /* AccountStatisticsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA273E5D2C4F680500F9CB13 /* AccountStatisticsViewModel.swift */; }; FA286AF52A3043C3008BD527 /* ConvenienceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA286AF42A3043C3008BD527 /* ConvenienceError.swift */; }; FA286B0B2A3043DB008BD527 /* CrossChainConfirmationViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA286AF72A3043DB008BD527 /* CrossChainConfirmationViewLayout.swift */; }; FA286B0C2A3043DB008BD527 /* CrossChainConfirmationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA286AF82A3043DB008BD527 /* CrossChainConfirmationRouter.swift */; }; @@ -2223,6 +2226,7 @@ FA38C9CB276305A3005C5577 /* WalletSendConfirmViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA38C9CA276305A3005C5577 /* WalletSendConfirmViewState.swift */; }; FA38C9CD276305B6005C5577 /* WalletSendConfirmViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA38C9CC276305B6005C5577 /* WalletSendConfirmViewModel.swift */; }; FA38C9CF27634A18005C5577 /* WalletSendConfirmViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA38C9CE27634A18005C5577 /* WalletSendConfirmViewModelFactory.swift */; }; + FA3F42F92C50C8EF00AA1397 /* ScamInfoFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3F42F82C50C8EF00AA1397 /* ScamInfoFetcher.swift */; }; FA3F5B17281A790A00BEF03B /* StakingAmountFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3F5B16281A790A00BEF03B /* StakingAmountFlow.swift */; }; FA3F5B67281BAF5200BEF03B /* StakingAmountRelaychainStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3F5B66281BAF5200BEF03B /* StakingAmountRelaychainStrategy.swift */; }; FA3F5B69281BAF5C00BEF03B /* StakingAmountParachainStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3F5B68281BAF5C00BEF03B /* StakingAmountParachainStrategy.swift */; }; @@ -2255,6 +2259,7 @@ FA4C3D122886794D00176398 /* SelfSizingTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4C3D112886794D00176398 /* SelfSizingTableView.swift */; }; FA4CC6642817C3AC00A7E85F /* StackedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4CC6632817C3AC00A7E85F /* StackedTableView.swift */; }; FA4CC666281801CB00A7E85F /* StakingUnitInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4CC665281801CB00A7E85F /* StakingUnitInfoView.swift */; }; + FA5032B22C4E58C500075909 /* AccountStatisticsPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5032B12C4E58C500075909 /* AccountStatisticsPresentable.swift */; }; FA5085AC2C33C6D4002DF97D /* SafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5085AA2C33C6D4002DF97D /* SafeArray.swift */; }; FA5085AD2C33C6D4002DF97D /* SafeDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5085AB2C33C6D4002DF97D /* SafeDictionary.swift */; }; FA5137AA29AC6F2F00560EBA /* PolkaswapDisclaimerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5137A129AC6F2F00560EBA /* PolkaswapDisclaimerViewModel.swift */; }; @@ -5135,6 +5140,7 @@ FA2222902BD239500031DE04 /* SoraSubqueryPriceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoraSubqueryPriceResponse.swift; sourceTree = ""; }; FA2222932BD2726F0031DE04 /* SkeletonLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonLabel.swift; sourceTree = ""; }; FA2222952BD272A30031DE04 /* SkeletonLoadableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonLoadableView.swift; sourceTree = ""; }; + FA236A402C4FA0A4009330F2 /* NomisJSONDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NomisJSONDecoder.swift; sourceTree = ""; }; FA24FEFD2B95C32200CD9E04 /* Decimal+DoubleValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+DoubleValue.swift"; sourceTree = ""; }; FA256982274CE5A400875A53 /* BalanceLockType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalanceLockType.swift; sourceTree = ""; }; FA256983274CE5A500875A53 /* BalanceLocks+Sort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BalanceLocks+Sort.swift"; sourceTree = ""; }; @@ -5185,6 +5191,8 @@ FA256A43274CE8BC00875A53 /* StoriesCollectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoriesCollectionItem.swift; sourceTree = ""; }; FA256A44274CE8BD00875A53 /* StoriesCollectionItem.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StoriesCollectionItem.xib; sourceTree = ""; }; FA256A47274CE8C200875A53 /* StakingMainInteractor+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StakingMainInteractor+Subscription.swift"; sourceTree = ""; }; + FA273E5B2C4F67A900F9CB13 /* AccountStatisticsViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountStatisticsViewModelFactory.swift; sourceTree = ""; }; + FA273E5D2C4F680500F9CB13 /* AccountStatisticsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountStatisticsViewModel.swift; sourceTree = ""; }; FA286AF42A3043C3008BD527 /* ConvenienceError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvenienceError.swift; sourceTree = ""; }; FA286AF72A3043DB008BD527 /* CrossChainConfirmationViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossChainConfirmationViewLayout.swift; sourceTree = ""; }; FA286AF82A3043DB008BD527 /* CrossChainConfirmationRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossChainConfirmationRouter.swift; sourceTree = ""; }; @@ -5382,6 +5390,7 @@ FA38C9CA276305A3005C5577 /* WalletSendConfirmViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSendConfirmViewState.swift; sourceTree = ""; }; FA38C9CC276305B6005C5577 /* WalletSendConfirmViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSendConfirmViewModel.swift; sourceTree = ""; }; FA38C9CE27634A18005C5577 /* WalletSendConfirmViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSendConfirmViewModelFactory.swift; sourceTree = ""; }; + FA3F42F82C50C8EF00AA1397 /* ScamInfoFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScamInfoFetcher.swift; sourceTree = ""; }; FA3F5B16281A790A00BEF03B /* StakingAmountFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingAmountFlow.swift; sourceTree = ""; }; FA3F5B66281BAF5200BEF03B /* StakingAmountRelaychainStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingAmountRelaychainStrategy.swift; sourceTree = ""; }; FA3F5B68281BAF5C00BEF03B /* StakingAmountParachainStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingAmountParachainStrategy.swift; sourceTree = ""; }; @@ -5414,6 +5423,7 @@ FA4C3D112886794D00176398 /* SelfSizingTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSizingTableView.swift; sourceTree = ""; }; FA4CC6632817C3AC00A7E85F /* StackedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackedTableView.swift; sourceTree = ""; }; FA4CC665281801CB00A7E85F /* StakingUnitInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingUnitInfoView.swift; sourceTree = ""; }; + FA5032B12C4E58C500075909 /* AccountStatisticsPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountStatisticsPresentable.swift; sourceTree = ""; }; FA5085AA2C33C6D4002DF97D /* SafeArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SafeArray.swift; sourceTree = ""; }; FA5085AB2C33C6D4002DF97D /* SafeDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SafeDictionary.swift; sourceTree = ""; }; FA5137A129AC6F2F00560EBA /* PolkaswapDisclaimerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PolkaswapDisclaimerViewModel.swift; sourceTree = ""; }; @@ -6539,6 +6549,7 @@ 0713097C28C63893002B17D0 /* ScamSyncService.swift */, 0713097E28C6F60D002B17D0 /* ScamSyncServiceFactory.swift */, 07B018D228C714B300E05510 /* ScamServiceOperationFactory.swift */, + FA3F42F82C50C8EF00AA1397 /* ScamInfoFetcher.swift */, ); path = ScamService; sourceTree = ""; @@ -10926,6 +10937,7 @@ 8E7E74939224B5DA444D4AFA /* AccountStatistics */ = { isa = PBXGroup; children = ( + FA273E5A2C4F679900F9CB13 /* ViewModel */, 4BF646F59913E95891915BDC /* AccountStatisticsProtocols.swift */, FDD63BEB84A28855006BE680 /* AccountStatisticsRouter.swift */, BB86E65E22C0AF7EDD0701A4 /* AccountStatisticsPresenter.swift */, @@ -10933,6 +10945,7 @@ 7C85B1F841C281165D7AACB1 /* AccountStatisticsViewController.swift */, 7743EA304BC53649D0473225 /* AccountStatisticsViewLayout.swift */, 62CD1B83902C1B5763476EFF /* AccountStatisticsAssembly.swift */, + FA5032B12C4E58C500075909 /* AccountStatisticsPresentable.swift */, ); path = AccountStatistics; sourceTree = ""; @@ -12825,6 +12838,15 @@ path = Requests; sourceTree = ""; }; + FA273E5A2C4F679900F9CB13 /* ViewModel */ = { + isa = PBXGroup; + children = ( + FA273E5B2C4F67A900F9CB13 /* AccountStatisticsViewModelFactory.swift */, + FA273E5D2C4F680500F9CB13 /* AccountStatisticsViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; FA286AF62A3043DB008BD527 /* CrossChainConfirmation */ = { isa = PBXGroup; children = ( @@ -15302,6 +15324,7 @@ FA4B098C2C4674C9001B73F9 /* Request */, FAD5FF2A2C46464B003201F5 /* NomisAccountStatisticsFetcher.swift */, FA4B098D2C47804F001B73F9 /* NomisRequestSigner.swift */, + FA236A402C4FA0A4009330F2 /* NomisJSONDecoder.swift */, ); path = Nomis; sourceTree = ""; @@ -16676,6 +16699,7 @@ 849014CB24AA8B75008F705E /* RootControllerAnimationCoordinator.swift in Sources */, 84DED3FC266651EB00A153BB /* ReferralCrowdloanViewModel.swift in Sources */, FA2FC82328B380C500CC0A42 /* StakingPoolState.swift in Sources */, + FA3F42F92C50C8EF00AA1397 /* ScamInfoFetcher.swift in Sources */, 8428766924ADF27D00D91AD8 /* AuthorizationPresentable.swift in Sources */, F4871DEE26D63E3E00D27F23 /* AnalyticsRewardDetailsViewModel.swift in Sources */, 84563D0524F466470055591D /* ManagedAccountItem.swift in Sources */, @@ -17327,6 +17351,7 @@ 849013DB24A927E2008F705E /* Logger.swift in Sources */, 84443BA226C123F100C33B5D /* Data+Random.swift in Sources */, 07DE95CA28A169A600E9C2CB /* AssetListSearchAssembly.swift in Sources */, + FA273E5E2C4F680500F9CB13 /* AccountStatisticsViewModel.swift in Sources */, 070CDD872ACBE59700F3F20A /* QRView.swift in Sources */, FA6262582AC2E35A005D3D95 /* MultiSelectNetworksRouter.swift in Sources */, 84C6800E24D6ECE800006BF5 /* ExpandableActionControl.swift in Sources */, @@ -17488,6 +17513,7 @@ 8454C26F2632BBAA00657DAD /* ExtrinsicProcessing.swift in Sources */, C661B3B227DF99A8005F1F7D /* AccountCreateViewController.swift in Sources */, 8461CC8326BC0006007460E4 /* MortalEraOperationFactory.swift in Sources */, + FA273E5C2C4F67AA00F9CB13 /* AccountStatisticsViewModelFactory.swift in Sources */, 8425EA8B25EA7AF200C307C9 /* UnappliedSlashes.swift in Sources */, 84893BFE24DA0000008F6A3F /* FieldStatus.swift in Sources */, 84F51053263AB440005D15AE /* StakingUnbondSetupLayout.swift in Sources */, @@ -17641,6 +17667,7 @@ AEE4E34D25E915ED00D6DF31 /* RewardCalculatorServiceProtocol.swift in Sources */, FA2FC82D28B3816D00CC0A42 /* StorageKeyDataExtractor.swift in Sources */, 849013DD24A927E2008F705E /* KeystoreExtensions.swift in Sources */, + FA236A412C4FA0A4009330F2 /* NomisJSONDecoder.swift in Sources */, FAD429062A86567F001D6A16 /* BackupCreatePasswordPresenter.swift in Sources */, FAD0679A2C2043FC0050291F /* ChainConnectionVisibilityHelper.swift in Sources */, FAD0068427EA255900C97E09 /* AboutTableViewCell.swift in Sources */, @@ -18673,6 +18700,7 @@ FAA0133728DA12B6000A5230 /* StakingPoolMainAssembly.swift in Sources */, 07FD95C027F4384900F07064 /* UIFont+dynamicSize.swift in Sources */, EB9D8D22AA13BF12F845856B /* ReferralCrowdloanProtocols.swift in Sources */, + FA5032B22C4E58C500075909 /* AccountStatisticsPresentable.swift in Sources */, FA7254292AC2E48500EC47A6 /* Caip2.swift in Sources */, 51FC48FA6FD4D2FB1781424D /* ReferralCrowdloanWireframe.swift in Sources */, 0F3E58FC800ED8722589F89E /* ReferralCrowdloanPresenter.swift in Sources */, diff --git a/fearless/ApplicationLayer/Services/AccountStatistics/AccountStatisticsFetching.swift b/fearless/ApplicationLayer/Services/AccountStatistics/AccountStatisticsFetching.swift index 61b0b620d..f63416e40 100644 --- a/fearless/ApplicationLayer/Services/AccountStatistics/AccountStatisticsFetching.swift +++ b/fearless/ApplicationLayer/Services/AccountStatistics/AccountStatisticsFetching.swift @@ -6,4 +6,6 @@ protocol AccountStatisticsFetching { address: String, cacheOptions: CachedNetworkRequestTrigger ) async throws -> AsyncThrowingStream, Error> + + func fetchStatistics(address: String) async throws -> AccountStatisticsResponse? } diff --git a/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift index 030916945..fc73c6e1f 100644 --- a/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift +++ b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift @@ -33,7 +33,22 @@ extension NomisAccountStatisticsFetcher: AccountStatisticsFetching { endpoint: "score" ) request.signingType = .custom(signer: signer) - + request.decoderType = .codable(jsonDecoder: NomisJSONDecoder()) return await networkWorker.performRequest(with: request, withCacheOptions: cacheOptions) } + + func fetchStatistics(address: String) async throws -> AccountStatisticsResponse? { + guard let baseURL = URL(string: "https://api.nomis.cc/api/v1/multichain-score/wallet/") else { + throw NomisAccountStatisticsFetcherError.badBaseURL + } + + let request = try NomisAccountStatisticsRequest( + baseURL: baseURL, + address: address, + endpoint: "score" + ) + request.signingType = .custom(signer: signer) + request.decoderType = .codable(jsonDecoder: NomisJSONDecoder()) + return try await networkWorker.performRequest(with: request) + } } diff --git a/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisJSONDecoder.swift b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisJSONDecoder.swift new file mode 100644 index 000000000..827551520 --- /dev/null +++ b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisJSONDecoder.swift @@ -0,0 +1,10 @@ +import Foundation + +final class NomisJSONDecoder: JSONDecoder { + override init() { + super.init() + let df = DateFormatter() + df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ" + dateDecodingStrategy = .formatted(df) + } +} diff --git a/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift b/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift new file mode 100644 index 000000000..0a5771390 --- /dev/null +++ b/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift @@ -0,0 +1,61 @@ +import Foundation + +protocol ScamInfoFetching { + func fetch(address: String) async throws -> ScamInfo? +} + +final class ScamInfoFetcher: ScamInfoFetching { + private let scamServiceOperationFactory: ScamServiceOperationFactoryProtocol + private let accountScoreFetching: AccountStatisticsFetching + private let operationQueue = OperationQueue() + + init( + scamServiceOperationFactory: ScamServiceOperationFactoryProtocol, + accountScoreFetching: AccountStatisticsFetching + ) { + self.scamServiceOperationFactory = scamServiceOperationFactory + self.accountScoreFetching = accountScoreFetching + } + + func fetch(address: String) async throws -> ScamInfo? { + let scamFeatureCheckingResult = try? await fetchScamInfo(address: address) + + guard scamFeatureCheckingResult == nil else { + return scamFeatureCheckingResult + } + + return try? await fetchAccountScore(address: address) + } + + // MARK: Private + + private func fetchScamInfo(address: String) async throws -> ScamInfo? { + try await withCheckedThrowingContinuation { continuation in + let operation = scamServiceOperationFactory.fetchScamInfoOperation(for: address) + + operation.completionBlock = { + do { + let scamInfo = try operation.extractNoCancellableResultData() + continuation.resume(returning: scamInfo) + } catch { + continuation.resume(throwing: error) + } + } + + operationQueue.addOperation(operation) + } + } + + private func fetchAccountScore(address: String) async throws -> ScamInfo? { + let score: AccountStatisticsResponse? = try await accountScoreFetching.fetchStatistics(address: address) + let scamInfo: ScamInfo? = score.flatMap { + guard ($0.data?.score).or(.zero) < 25 else { + return nil + } + + return ScamInfo(name: "Low score", address: address, type: .lowScore, subtype: "Low network activity") + } + + return scamInfo + } +} diff --git a/fearless/Common/Model/AccountStatistics.swift b/fearless/Common/Model/AccountStatistics.swift index ded5750a3..3bd4a06c8 100644 --- a/fearless/Common/Model/AccountStatistics.swift +++ b/fearless/Common/Model/AccountStatistics.swift @@ -6,10 +6,23 @@ struct AccountStatisticsResponse: Decodable { struct AccountStatistics: Decodable { let score: Decimal? + let address: String? let stats: AccountStatisticsData? } struct AccountStatisticsData: Decodable { + enum CodingKeys: String, CodingKey { + case nativeBalanceUSD + case holdTokensBalanceUSD + case walletAge + case totalTransactions + case totalRejectedTransactions + case averageTransactionTime + case maxTransactionTime + case minTransactionTime + case scoredAt + } + let nativeBalanceUSD: Decimal? let holdTokensBalanceUSD: Decimal? let walletAge: Int? @@ -18,4 +31,19 @@ struct AccountStatisticsData: Decodable { let averageTransactionTime: Decimal? let maxTransactionTime: Decimal? let minTransactionTime: Decimal? + let scoredAt: Date? + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + nativeBalanceUSD = try? container.decodeIfPresent(Decimal.self, forKey: .nativeBalanceUSD) + holdTokensBalanceUSD = try? container.decodeIfPresent(Decimal.self, forKey: .holdTokensBalanceUSD) + walletAge = try? container.decodeIfPresent(Int.self, forKey: .walletAge) + totalTransactions = try? container.decodeIfPresent(Int.self, forKey: .totalTransactions) + totalRejectedTransactions = try? container.decodeIfPresent(Int.self, forKey: .totalRejectedTransactions) + averageTransactionTime = try? container.decodeIfPresent(Decimal.self, forKey: .averageTransactionTime) + maxTransactionTime = try? container.decodeIfPresent(Decimal.self, forKey: .maxTransactionTime) + minTransactionTime = try? container.decodeIfPresent(Decimal.self, forKey: .minTransactionTime) + scoredAt = try? container.decodeIfPresent(Date.self, forKey: .scoredAt) + } } diff --git a/fearless/Common/Model/ScamInfo.swift b/fearless/Common/Model/ScamInfo.swift index 995f1a385..f72bda82d 100644 --- a/fearless/Common/Model/ScamInfo.swift +++ b/fearless/Common/Model/ScamInfo.swift @@ -24,6 +24,7 @@ struct ScamInfo: Identifiable, Codable, Equatable, Hashable { case donation case exchange case sanctions + case lowScore init?(from string: String) { self.init(rawValue: string.lowercased()) @@ -54,6 +55,8 @@ struct ScamInfo: Identifiable, Codable, Equatable, Hashable { case .sanctions: return R.string.localizable .scamDescriptionSanctionsStub(assetName, preferredLanguages: locale.rLanguages) + case .lowScore: + return "This account has a low activity. Ensure that recipient isn't a scammer" } } } diff --git a/fearless/Common/View/AccountScore/AccountScoreView.swift b/fearless/Common/View/AccountScore/AccountScoreView.swift index 021a98e3b..8e2b7cea1 100644 --- a/fearless/Common/View/AccountScore/AccountScoreView.swift +++ b/fearless/Common/View/AccountScore/AccountScoreView.swift @@ -2,14 +2,22 @@ import UIKit import Cosmos class AccountScoreView: UIView { + private var viewModel: AccountScoreViewModel? + let starView: CosmosView = { let view = CosmosView() view.settings.totalStars = 1 + view.settings.starSize = 15 + view.settings.textMargin = 2 + view.settings.textFont = .h6Title + view.settings.passTouchesToSuperview = false + view.settings.fillMode = .precise return view }() override init(frame: CGRect) { super.init(frame: frame) + isHidden = true addSubviews() setupConstraints() } @@ -19,24 +27,29 @@ class AccountScoreView: UIView { fatalError("init(coder:) has not been implemented") } - func bind(viewModel: AccountScoreViewModel) { - starView.text = viewModel.accountScoreLabelText + func bind(viewModel: AccountScoreViewModel?) { + self.viewModel = viewModel + viewModel?.setup(with: self) + } + + func bind(score: Int, rate: AccountScoreRate) { + isHidden = false + starView.text = "\(score)" - starView.settings.textFont = .h5Title - if let color = viewModel.rate.color { + if let color = rate.color { starView.settings.emptyBorderColor = color starView.settings.filledColor = color starView.settings.filledBorderColor = color starView.settings.textColor = color } - switch viewModel.rate { + switch rate { case .high: - starView.settings.fillMode = .full + starView.rating = 5 case .medium: - starView.settings.fillMode = .half + starView.rating = 2.5 case .low: - starView.settings.fillMode = .precise + starView.rating = 0 } } diff --git a/fearless/Common/View/ScamWarningExpandableView.swift b/fearless/Common/View/ScamWarningExpandableView.swift index 470ac3ab9..75a306e84 100644 --- a/fearless/Common/View/ScamWarningExpandableView.swift +++ b/fearless/Common/View/ScamWarningExpandableView.swift @@ -51,7 +51,11 @@ final class ScamWarningExpandableView: UIView { }() private let mainCloudView = UIView() - private let expandableCloudView = UIView() + private let expandableCloudView: UIView = { + let view = UIView() + view.isHidden = true + return view + }() private let nameLabel: UILabel = { let label = UILabel() @@ -222,6 +226,7 @@ final class ScamWarningExpandableView: UIView { @objc private func handleTapGesture() { let isOpen = indicator.isActivated let offset = isOpen ? -Constants.expandViewHeight : 0 + expandableCloudView.isHidden = isOpen expandableCloudView.snp.updateConstraints { make in make.top.equalTo(mainCloudView.snp.bottom).offset(offset) } diff --git a/fearless/Common/View/SkeletonLoadableView.swift b/fearless/Common/View/SkeletonLoadableView.swift index 8012c1867..110767db7 100644 --- a/fearless/Common/View/SkeletonLoadableView.swift +++ b/fearless/Common/View/SkeletonLoadableView.swift @@ -16,24 +16,27 @@ extension SkeletonLoadableView { guard skeletonView == nil else { return } + let skeletonView = Skrull( size: skeletonSize, decorations: [], skeletons: [ - SingleSkeleton.createRow(position: CGPoint(x: 0, y: 0), size: skeletonSize) + SingleSkeleton.createRow(position: CGPoint(x: 0.5, y: 0.5), size: skeletonSize) ] ) .fillSkeletonStart(R.color.colorSkeletonStart()!) .fillSkeletonEnd(color: R.color.colorSkeletonEnd()!) .build() - skeletonView.frame = CGRect(origin: CGPoint(x: -skeletonSize.width, y: skeletonSize.height / 2), size: skeletonSize) + skeletonView.frame = CGRect( + origin: CGPoint(x: frame.size.width - skeletonSize.width, y: frame.size.height / 2 - skeletonSize.height / 2), + size: CGSize(width: skeletonSize.width, height: skeletonSize.height) + ) skeletonView.autoresizingMask = [] container.addSubview(skeletonView) - self.skeletonView = skeletonView - skeletonView.startSkrulling() + self.skeletonView = skeletonView } func stopSkeletonAnimation() { @@ -43,10 +46,12 @@ extension SkeletonLoadableView { } func updateSkeletonLayout() { - guard let skeletonView = skeletonView else { + guard skeletonView != nil else { return } - - skeletonView.frame = CGRect(origin: CGPoint(x: -skeletonSize.width / 2, y: skeletonSize.height / 2), size: skeletonSize) + skeletonView?.stopSkrulling() + skeletonView?.removeFromSuperview() + skeletonView = nil + startSkeletonAnimation() } } diff --git a/fearless/Common/View/TitleCopyableValueView.swift b/fearless/Common/View/TitleCopyableValueView.swift index 3da0b1944..22c9e1880 100644 --- a/fearless/Common/View/TitleCopyableValueView.swift +++ b/fearless/Common/View/TitleCopyableValueView.swift @@ -9,6 +9,10 @@ final class TitleCopyableValueView: TitleValueView { return button }() + override init(skeletonSize: CGSize = .zero) { + super.init(skeletonSize: skeletonSize) + } + override init(frame: CGRect) { super.init(frame: frame) setupCopyButton() diff --git a/fearless/Common/View/TitleValueView.swift b/fearless/Common/View/TitleValueView.swift index 72d27adbc..e60f98551 100644 --- a/fearless/Common/View/TitleValueView.swift +++ b/fearless/Common/View/TitleValueView.swift @@ -13,8 +13,8 @@ class TitleValueView: UIView { return label }() - let valueLabel: UILabel = { - let label = UILabel() + let valueLabel: SkeletonLabel = { + let label = SkeletonLabel(skeletonSize: CGSize(width: 70, height: 14)) label.textColor = R.color.colorWhite() label.font = UIFont.p1Paragraph label.textAlignment = .right @@ -36,7 +36,7 @@ class TitleValueView: UIView { let activityIndicator: UIActivityIndicatorView = { let view = UIActivityIndicatorView() view.hidesWhenStopped = true - view.style = .white + view.style = UIActivityIndicatorView.Style.medium return view }() @@ -50,6 +50,12 @@ class TitleValueView: UIView { } } + init(skeletonSize: CGSize = .zero) { + super.init(frame: .zero) + setupLayout() + valueLabel.skeletonSize = skeletonSize + } + override init(frame: CGRect) { super.init(frame: frame) setupLayout() diff --git a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift index 254396ef9..8495f9fbe 100644 --- a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift +++ b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift @@ -27,7 +27,42 @@ enum AccountScoreRate { } } -struct AccountScoreViewModel { - let accountScoreLabelText: String - let rate: AccountScoreRate +class AccountScoreViewModel { + private let fetcher: AccountStatisticsFetching + let address: String + + weak var view: AccountScoreView? + + init(fetcher: AccountStatisticsFetching, address: String) { + self.fetcher = fetcher + self.address = address + } + + func setup(with view: AccountScoreView?) { + self.view = view + + Task { + do { + let stream = try await fetcher.subscribeForStatistics(address: address, cacheOptions: .onAll) + for try await statistics in stream { + handle(response: statistics.value) + } + } catch { + print("Account statistics fetching error: ", error) + } + } + } + + private func handle(response: AccountStatisticsResponse?) { + guard let score = response?.data?.score else { + return + } + + let rate = AccountScoreRate(from: score) + let intScore = ((score * 100.0) as NSDecimalNumber).intValue + + DispatchQueue.main.async { [weak self] in + self?.view?.bind(score: intScore, rate: rate) + } + } } diff --git a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift index a48b484e7..6cf4606eb 100644 --- a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift +++ b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift @@ -1,17 +1,17 @@ -import Foundation - -protocol AccountScoreViewModelFactory { - func buildViewModel(from accountStatistics: AccountStatistics) -> AccountScoreViewModel? -} - -final class AccountScoreViewModelFactoryImpl: AccountScoreViewModelFactory { - func buildViewModel(from accountStatistics: AccountStatistics) -> AccountScoreViewModel? { - guard let score = accountStatistics.score else { - return nil - } - - let rate = AccountScoreRate(from: score) - let intScore = ((score * 100.0) as NSDecimalNumber).intValue - return AccountScoreViewModel(accountScoreLabelText: "\(intScore)", rate: rate) - } -} +// import Foundation +// +// protocol AccountScoreViewModelFactory { +// func buildViewModel(from accountStatistics: AccountStatistics) -> AccountScoreViewModel? +// } +// +// final class AccountScoreViewModelFactoryImpl: AccountScoreViewModelFactory { +// func buildViewModel(from accountStatistics: AccountStatistics) -> AccountScoreViewModel? { +// guard let score = accountStatistics.score else { +// return nil +// } +// +// let rate = AccountScoreRate(from: score) +// let intScore = ((score * 100.0) as NSDecimalNumber).intValue +// return AccountScoreViewModel(accountScoreLabelText: "\(intScore)", rate: rate) +// } +// } diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift b/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift index 08ec322cc..468144b6f 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift @@ -1,17 +1,20 @@ import UIKit import SoraFoundation +import SSFNetwork final class AccountStatisticsAssembly { - static func configureModule() -> AccountStatisticsModuleCreationResult? { + static func configureModule(address: String) -> AccountStatisticsModuleCreationResult? { let localizationManager = LocalizationManager.shared - let interactor = AccountStatisticsInteractor() + let accountScoreFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) + let interactor = AccountStatisticsInteractor(accountScoreFetcher: accountScoreFetcher, address: address) let router = AccountStatisticsRouter() let presenter = AccountStatisticsPresenter( interactor: interactor, router: router, - localizationManager: localizationManager + localizationManager: localizationManager, + viewModelFactory: AccountStatisticsViewModelFactoryImpl() ) let view = AccountStatisticsViewController( diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift b/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift index ed211c1bc..4ca3b7c13 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift @@ -1,11 +1,21 @@ import UIKit -protocol AccountStatisticsInteractorOutput: AnyObject {} +protocol AccountStatisticsInteractorOutput: AnyObject { + func didReceiveAccountStatistics(_ response: AccountStatisticsResponse?) + func didReceiveAccountStatisticsError(_ error: Error) +} final class AccountStatisticsInteractor { // MARK: - Private properties private weak var output: AccountStatisticsInteractorOutput? + private let accountScoreFetcher: AccountStatisticsFetching + private let address: String + + init(accountScoreFetcher: AccountStatisticsFetching, address: String) { + self.accountScoreFetcher = accountScoreFetcher + self.address = address + } } // MARK: - AccountStatisticsInteractorInput @@ -14,4 +24,18 @@ extension AccountStatisticsInteractor: AccountStatisticsInteractorInput { func setup(with output: AccountStatisticsInteractorOutput) { self.output = output } + + func fetchAccountStatistics() { + Task { + do { + let stream = try await accountScoreFetcher.subscribeForStatistics(address: address, cacheOptions: .onAll) + + for try await accountScore in stream { + output?.didReceiveAccountStatistics(accountScore.value) + } + } catch { + output?.didReceiveAccountStatisticsError(error) + } + } + } } diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsPresentable.swift b/fearless/Modules/AccountStatistics/AccountStatisticsPresentable.swift new file mode 100644 index 000000000..12bb8521c --- /dev/null +++ b/fearless/Modules/AccountStatistics/AccountStatisticsPresentable.swift @@ -0,0 +1,27 @@ +import Foundation + +protocol AccountScorePresentable { + func presentAccountScore( + address: String, + from view: ControllerBackedProtocol? + ) +} + +extension AccountScorePresentable { + func presentAccountScore( + address: String, + from view: ControllerBackedProtocol? + ) { + guard let controller = view?.controller else { + return + } + + let accountScoreViewController = AccountStatisticsAssembly.configureModule(address: address)?.view.controller + + guard let accountScoreViewController else { + return + } + + controller.present(accountScoreViewController, animated: true) + } +} diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift b/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift index c6d836819..2f9925eb2 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift @@ -1,10 +1,13 @@ import Foundation import SoraFoundation -protocol AccountStatisticsViewInput: ControllerBackedProtocol {} +protocol AccountStatisticsViewInput: ControllerBackedProtocol { + func didReceive(viewModel: AccountStatisticsViewModel?) +} protocol AccountStatisticsInteractorInput: AnyObject { func setup(with output: AccountStatisticsInteractorOutput) + func fetchAccountStatistics() } final class AccountStatisticsPresenter { @@ -13,20 +16,34 @@ final class AccountStatisticsPresenter { private weak var view: AccountStatisticsViewInput? private let router: AccountStatisticsRouterInput private let interactor: AccountStatisticsInteractorInput + private let viewModelFactory: AccountStatisticsViewModelFactory + + private var accountStatistics: AccountStatistics? // MARK: - Constructors init( interactor: AccountStatisticsInteractorInput, router: AccountStatisticsRouterInput, - localizationManager: LocalizationManagerProtocol + localizationManager: LocalizationManagerProtocol, + viewModelFactory: AccountStatisticsViewModelFactory ) { self.interactor = interactor self.router = router + self.viewModelFactory = viewModelFactory + self.localizationManager = localizationManager } // MARK: - Private methods + + private func provideViewModel() { + let viewModel = viewModelFactory.buildViewModel(accountScore: accountStatistics, locale: selectedLocale) + + DispatchQueue.main.async { [weak self] in + self?.view?.didReceive(viewModel: viewModel) + } + } } // MARK: - AccountStatisticsViewOutput @@ -34,13 +51,37 @@ final class AccountStatisticsPresenter { extension AccountStatisticsPresenter: AccountStatisticsViewOutput { func didLoad(view: AccountStatisticsViewInput) { self.view = view + + DispatchQueue.main.async { + view.didReceive(viewModel: nil) + } + interactor.setup(with: self) + interactor.fetchAccountStatistics() + } + + func didTapCloseButton() { + router.dismiss(view: view) + } + + func didTapCopyAddress() { + router.presentStatus( + with: AddressCopiedEvent(locale: selectedLocale), + animated: true + ) } } // MARK: - AccountStatisticsInteractorOutput -extension AccountStatisticsPresenter: AccountStatisticsInteractorOutput {} +extension AccountStatisticsPresenter: AccountStatisticsInteractorOutput { + func didReceiveAccountStatistics(_ response: AccountStatisticsResponse?) { + accountStatistics = response?.data + provideViewModel() + } + + func didReceiveAccountStatisticsError(_: Error) {} +} // MARK: - Localizable diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsProtocols.swift b/fearless/Modules/AccountStatistics/AccountStatisticsProtocols.swift index 8b3af8c89..d4e4cfb2d 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsProtocols.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsProtocols.swift @@ -3,7 +3,7 @@ typealias AccountStatisticsModuleCreationResult = ( input: AccountStatisticsModuleInput ) -protocol AccountStatisticsRouterInput: AnyObject {} +protocol AccountStatisticsRouterInput: AnyObject, AnyDismissable, ApplicationStatusPresentable {} protocol AccountStatisticsModuleInput: AnyObject {} diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift b/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift index f6475ce08..f73c7a677 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift @@ -3,6 +3,8 @@ import SoraFoundation protocol AccountStatisticsViewOutput: AnyObject { func didLoad(view: AccountStatisticsViewInput) + func didTapCloseButton() + func didTapCopyAddress() } final class AccountStatisticsViewController: UIViewController, ViewHolder { @@ -37,14 +39,31 @@ final class AccountStatisticsViewController: UIViewController, ViewHolder { override func viewDidLoad() { super.viewDidLoad() output.didLoad(view: self) + setupActions() } // MARK: - Private methods + + private func setupActions() { + rootView.closeButton.addAction { [weak self] in + self?.output.didTapCloseButton() + } + rootView.topBar.backButton.addAction { [weak self] in + self?.output.didTapCloseButton() + } + rootView.addressView.onСopied = { [weak self] in + self?.output.didTapCopyAddress() + } + } } // MARK: - AccountStatisticsViewInput -extension AccountStatisticsViewController: AccountStatisticsViewInput {} +extension AccountStatisticsViewController: AccountStatisticsViewInput { + func didReceive(viewModel: AccountStatisticsViewModel?) { + rootView.bind(viewModel: viewModel) + } +} // MARK: - Localizable diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift b/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift index 168cf2703..2359a75d8 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift @@ -14,14 +14,19 @@ final class AccountStatisticsViewLayout: UIView { let ratingView: CosmosView = { var settings = CosmosSettings() settings.starSize = 30 - settings.fillMode = .full - return CosmosView(settings: settings) + settings.fillMode = .precise + settings.updateOnTouch = false + let cosmosView = CosmosView(settings: settings) + cosmosView.rating = 5 + return cosmosView }() let descriptionLabel: UILabel = { let label = UILabel() label.font = .p2Paragraph label.textColor = R.color.colorLightGray() + label.numberOfLines = 0 + label.textAlignment = .center return label }() @@ -34,7 +39,18 @@ final class AccountStatisticsViewLayout: UIView { let addressView = CopyableLabelView() - let statsBackgroundView = TriangularedView() + let contentBackgroundView = UIView() + let statsBackgroundView: TriangularedView = { + let view = TriangularedView() + view.fillColor = R.color.colorSemiBlack()! + view.highlightedFillColor = R.color.colorSemiBlack()! + view.shadowColor = .clear + view.strokeColor = R.color.colorWhite8()! + view.highlightedStrokeColor = R.color.colorWhite8()! + view.strokeWidth = 0.5 + view.layer.shadowOpacity = 0 + return view + }() let stackView = UIFactory.default.createVerticalStackView(spacing: 8) @@ -48,7 +64,11 @@ final class AccountStatisticsViewLayout: UIView { let maxTransactionTimeView = makeRowView() let minTransactionTimeView = makeRowView() - let closeButton = TriangularedButton() + let closeButton: TriangularedButton = { + let button = TriangularedButton() + button.applyEnabledStyle() + return button + }() var locale: Locale = .current { didSet { @@ -61,6 +81,9 @@ final class AccountStatisticsViewLayout: UIView { addSubviews() setupConstraints() + + contentBackgroundView.backgroundColor = R.color.colorBlack19() + backgroundColor = R.color.colorBlack19() } @available(*, unavailable) @@ -68,6 +91,43 @@ final class AccountStatisticsViewLayout: UIView { fatalError("init(coder:) has not been implemented") } + func bind(viewModel: AccountStatisticsViewModel?) { + if let rating = viewModel?.rating { + ratingView.rating = rating + } + + if let address = viewModel?.addressViewText { + addressView.bind(title: address) + } + + updatedView.isHidden = viewModel != nil && viewModel?.updatedLabelText == nil + nativeBalanceUsdView.isHidden = viewModel != nil && viewModel?.nativeBalanceUsdLabelText == nil + holdTokensUsdView.isHidden = viewModel != nil && viewModel?.holdTokensUsdLabelText == nil + walletAgeView.isHidden = viewModel != nil && viewModel?.walletAgeLabelText == nil + totalTransactionsView.isHidden = viewModel != nil && viewModel?.totalTransactionsLabelText == nil + rejectedTransactionsView.isHidden = viewModel != nil && viewModel?.rejectedTransactionsLabelText == nil + avgTransactionTimeView.isHidden = viewModel != nil && viewModel?.avgTransactionTimeLabelText == nil + maxTransactionTimeView.isHidden = viewModel != nil && viewModel?.maxTransactionTimeLabelText == nil + minTransactionTimeView.isHidden = viewModel != nil && viewModel?.minTransactionTimeLabelText == nil + + scoreLabel.text = viewModel?.scoreLabelText + updatedView.valueLabel.updateTextWithLoading(viewModel?.updatedLabelText) + nativeBalanceUsdView.valueLabel.updateTextWithLoading(viewModel?.nativeBalanceUsdLabelText) + holdTokensUsdView.valueLabel.updateTextWithLoading(viewModel?.holdTokensUsdLabelText) + walletAgeView.valueLabel.updateTextWithLoading(viewModel?.walletAgeLabelText) + totalTransactionsView.valueLabel.updateTextWithLoading(viewModel?.totalTransactionsLabelText) + rejectedTransactionsView.valueLabel.updateTextWithLoading(viewModel?.rejectedTransactionsLabelText) + avgTransactionTimeView.valueLabel.updateTextWithLoading(viewModel?.avgTransactionTimeLabelText) + maxTransactionTimeView.valueLabel.updateTextWithLoading(viewModel?.maxTransactionTimeLabelText) + minTransactionTimeView.valueLabel.updateTextWithLoading(viewModel?.minTransactionTimeLabelText) + + if let color = viewModel?.rate.color { + ratingView.settings.emptyBorderColor = color + ratingView.settings.filledColor = color + ratingView.settings.filledBorderColor = color + } + } + // MARK: - Private methods private func addSubviews() { @@ -75,7 +135,12 @@ final class AccountStatisticsViewLayout: UIView { addSubview(scrollView) addSubview(closeButton) - scrollView.addSubview(statsBackgroundView) + scrollView.addSubview(contentBackgroundView) + contentBackgroundView.addSubview(ratingView) + contentBackgroundView.addSubview(descriptionLabel) + contentBackgroundView.addSubview(scoreLabel) + contentBackgroundView.addSubview(addressView) + contentBackgroundView.addSubview(statsBackgroundView) statsBackgroundView.addSubview(stackView) [updatedView, @@ -98,30 +163,28 @@ final class AccountStatisticsViewLayout: UIView { make.height.equalTo(56) } - closeButton.snp.makeConstraints { make in - make.leading.trailing.equalToSuperview().inset(16) - make.height.equalTo(48) - make.bottom.equalTo(safeAreaInsets).inset(16) - } - scrollView.snp.makeConstraints { make in + make.width.equalToSuperview() make.top.equalTo(topBar.snp.bottom) - make.bottom.equalTo(closeButton).offset(16) make.leading.trailing.equalToSuperview() + make.bottom.equalTo(closeButton.snp.top).offset(-16) } - statsBackgroundView.snp.makeConstraints { make in - make.edges.equalToSuperview() + closeButton.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(16) + make.height.equalTo(48) + make.bottom.equalTo(safeAreaInsets).inset(16) } - stackView.snp.makeConstraints { make in + contentBackgroundView.snp.makeConstraints { make in make.edges.equalToSuperview() + make.width.equalTo(self) } ratingView.snp.makeConstraints { make in - make.top.equalTo(topBar.snp.bottom).offset(16) make.centerX.equalToSuperview() make.height.equalTo(30) + make.top.equalToSuperview() } descriptionLabel.snp.makeConstraints { make in @@ -138,6 +201,17 @@ final class AccountStatisticsViewLayout: UIView { make.top.equalTo(scoreLabel.snp.bottom).offset(8) make.centerX.equalToSuperview() } + + statsBackgroundView.snp.makeConstraints { make in + make.top.equalTo(addressView.snp.bottom).offset(16) + make.leading.trailing.equalToSuperview().inset(16) + make.bottom.equalToSuperview() + } + + stackView.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.leading.trailing.equalToSuperview().inset(16) + } } private func applyLocalization() { @@ -152,21 +226,28 @@ final class AccountStatisticsViewLayout: UIView { avgTransactionTimeView.titleLabel.text = R.string.localizable.accountStatsAvgTransactionTimeTitle(preferredLanguages: locale.rLanguages) maxTransactionTimeView.titleLabel.text = R.string.localizable.accountStatsMaxTransactionTimeTitle(preferredLanguages: locale.rLanguages) minTransactionTimeView.titleLabel.text = R.string.localizable.accountStatsMinTransactionsTimeTitle(preferredLanguages: locale.rLanguages) + closeButton.imageWithTitleView?.title = R.string.localizable.commonClose(preferredLanguages: locale.rLanguages) } - private func setupRowViewConstraints(_ view: UIView) { + private func setupRowViewConstraints(_ view: TitleValueView) { view.snp.makeConstraints { make in make.height.equalTo(48) - make.leading.trailing.equalToSuperview().inset(16) + make.leading.trailing.equalToSuperview() + } + + view.valueLabel.snp.makeConstraints { make in + make.trailing.equalToSuperview() } } private static func makeRowView() -> TitleValueView { let view = TitleValueView() - view.titleLabel.font = .p1Paragraph - view.titleLabel.textColor = R.color.colorWhite() - view.valueLabel.font = .p1Paragraph + view.titleLabel.font = .h5Title + view.titleLabel.textColor = R.color.colorWhite50() + view.titleLabel.numberOfLines = 0 + view.valueLabel.font = .h5Title view.valueLabel.textColor = R.color.colorWhite() + view.valueLabel.numberOfLines = 2 view.borderView.isHidden = true view.equalsLabelsWidth = true view.valueLabel.lineBreakMode = .byTruncatingTail diff --git a/fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModel.swift b/fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModel.swift new file mode 100644 index 000000000..40b22e360 --- /dev/null +++ b/fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModel.swift @@ -0,0 +1,17 @@ +import Foundation + +struct AccountStatisticsViewModel { + let rating: Double? + let scoreLabelText: String? + let rate: AccountScoreRate + let addressViewText: String? + let updatedLabelText: String? + let nativeBalanceUsdLabelText: String? + let holdTokensUsdLabelText: String? + let walletAgeLabelText: String? + let totalTransactionsLabelText: String? + let rejectedTransactionsLabelText: String? + let avgTransactionTimeLabelText: String? + let maxTransactionTimeLabelText: String? + let minTransactionTimeLabelText: String? +} diff --git a/fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModelFactory.swift b/fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModelFactory.swift new file mode 100644 index 000000000..0706eac28 --- /dev/null +++ b/fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModelFactory.swift @@ -0,0 +1,58 @@ +import Foundation +import SoraFoundation + +protocol AccountStatisticsViewModelFactory { + func buildViewModel(accountScore: AccountStatistics?, locale: Locale) -> AccountStatisticsViewModel? +} + +final class AccountStatisticsViewModelFactoryImpl: AccountStatisticsViewModelFactory { + private lazy var timeFormatter = TotalTimeFormatter() + + func buildViewModel(accountScore: AccountStatistics?, locale: Locale) -> AccountStatisticsViewModel? { + guard let accountScore, let score = accountScore.score else { + return nil + } + let rate = AccountScoreRate(from: score) + let intScore = ((score * 100.0) as NSDecimalNumber).intValue + let doubleScore = (5 * score as NSDecimalNumber).doubleValue + + let avgTxTime = (accountScore.stats?.averageTransactionTime).flatMap { ($0 as NSDecimalNumber).doubleValue } + let maxTxTime = (accountScore.stats?.maxTransactionTime).flatMap { ($0 as NSDecimalNumber).doubleValue } + let minTxTime = (accountScore.stats?.minTransactionTime).flatMap { ($0 as NSDecimalNumber).doubleValue } + + let fiatFomatter = createFiatBalanceFormatter(locale: locale) + let dateFormatter = DateFormatter.shortDate.value(for: locale) + + let updatedText = accountScore.stats?.scoredAt.flatMap { dateFormatter.string(from: $0) } + let nativeBalanceText = accountScore.stats?.nativeBalanceUSD.flatMap { fiatFomatter.stringFromDecimal($0) } + let holdTokensText = accountScore.stats?.holdTokensBalanceUSD.flatMap { fiatFomatter.stringFromDecimal($0) } + let walletAgeText = accountScore.stats?.walletAge.flatMap { "\($0) month" } + let totalTransactionsText = accountScore.stats?.totalTransactions.flatMap { "\($0)" } + let rejectedTransactionsText = accountScore.stats?.totalRejectedTransactions.flatMap { "\($0)" } + let avgTransactionTimeText = avgTxTime.flatMap { try? timeFormatter.string(from: $0) } + let maxTransactionTimeText = maxTxTime.flatMap { try? timeFormatter.string(from: $0) } + let minTransactionTimeText = minTxTime.flatMap { try? timeFormatter.string(from: $0) } + + return AccountStatisticsViewModel( + rating: doubleScore, + scoreLabelText: "\(intScore)", + rate: rate, + addressViewText: accountScore.address, + updatedLabelText: updatedText, + nativeBalanceUsdLabelText: nativeBalanceText, + holdTokensUsdLabelText: holdTokensText, + walletAgeLabelText: walletAgeText, + totalTransactionsLabelText: totalTransactionsText, + rejectedTransactionsLabelText: rejectedTransactionsText, + avgTransactionTimeLabelText: avgTransactionTimeText, + maxTransactionTimeLabelText: maxTransactionTimeText, + minTransactionTimeLabelText: minTransactionTimeText + ) + } + + // MARK: Private + + private func createFiatBalanceFormatter(locale: Locale) -> LocalizableDecimalFormatting { + AssetBalanceFormatterFactory().createTokenFormatter(for: AssetBalanceDisplayInfo.forCurrency(.defaultCurrency()), usageCase: .fiat).value(for: locale) + } +} diff --git a/fearless/Modules/BackupWallet/BackupWalletAssembly.swift b/fearless/Modules/BackupWallet/BackupWalletAssembly.swift index 5a14e709d..0d24dcafc 100644 --- a/fearless/Modules/BackupWallet/BackupWalletAssembly.swift +++ b/fearless/Modules/BackupWallet/BackupWalletAssembly.swift @@ -3,6 +3,7 @@ import SoraFoundation import RobinHood import SoraKeystore import SSFCloudStorage +import SSFNetwork final class BackupWalletAssembly { static func configureModule( @@ -34,13 +35,19 @@ final class BackupWalletAssembly { operationManager: OperationManagerFacade.sharedManager ) let router = BackupWalletRouter() + let accountScoreFetcher = NomisAccountStatisticsFetcher( + networkWorker: NetworkWorkerImpl(), + signer: NomisRequestSigner() + ) + let viewModelFactory = BackupWalletViewModelFactory(accountScoreFetcher: accountScoreFetcher) let presenter = BackupWalletPresenter( wallet: wallet, interactor: interactor, router: router, logger: logger, - localizationManager: localizationManager + localizationManager: localizationManager, + viewModelFactory: viewModelFactory ) let view = BackupWalletViewController( diff --git a/fearless/Modules/BackupWallet/BackupWalletPresenter.swift b/fearless/Modules/BackupWallet/BackupWalletPresenter.swift index f9d90dd19..1a85093ce 100644 --- a/fearless/Modules/BackupWallet/BackupWalletPresenter.swift +++ b/fearless/Modules/BackupWallet/BackupWalletPresenter.swift @@ -22,9 +22,7 @@ final class BackupWalletPresenter { private let logger: LoggerProtocol private let wallet: MetaAccountModel - private lazy var viewModelFactory: BackupWalletViewModelFactoryProtocol = { - BackupWalletViewModelFactory() - }() + private var viewModelFactory: BackupWalletViewModelFactoryProtocol private var balanceInfo: WalletBalanceInfo? private var chains: [ChainModel] = [] @@ -42,12 +40,15 @@ final class BackupWalletPresenter { interactor: BackupWalletInteractorInput, router: BackupWalletRouterInput, logger: LoggerProtocol, - localizationManager: LocalizationManagerProtocol + localizationManager: LocalizationManagerProtocol, + viewModelFactory: BackupWalletViewModelFactoryProtocol ) { self.wallet = wallet self.interactor = interactor self.router = router self.logger = logger + self.viewModelFactory = viewModelFactory + self.localizationManager = localizationManager } diff --git a/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift b/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift index 5f2817dfe..410880d5d 100644 --- a/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift +++ b/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift @@ -34,6 +34,11 @@ enum BackupWalletOptions: Int, CaseIterable { final class BackupWalletViewModelFactory: BackupWalletViewModelFactoryProtocol { private lazy var assetBalanceFormatterFactory = AssetBalanceFormatterFactory() + private let accountScoreFetcher: AccountStatisticsFetching + + init(accountScoreFetcher: AccountStatisticsFetching) { + self.accountScoreFetcher = accountScoreFetcher + } func createViewModel( from wallet: MetaAccountModel, @@ -147,6 +152,9 @@ final class BackupWalletViewModelFactory: BackupWalletViewModelFactoryProtocol { balance: WalletBalanceInfo?, locale: Locale ) -> WalletsManagmentCellViewModel { + let address = wallet.ethereumAddress?.toHex(includePrefix: true) + let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0) } + var fiatBalance: String = "" var dayChange: NSAttributedString? if let balance = balance { @@ -164,7 +172,8 @@ final class BackupWalletViewModelFactory: BackupWalletViewModelFactoryProtocol { isSelected: false, walletName: wallet.name, fiatBalance: fiatBalance, - dayChange: dayChange + dayChange: dayChange, + accountScoreViewModel: accountScoreViewModel ) } diff --git a/fearless/Modules/Contacts/Cell/ContactTableCell.swift b/fearless/Modules/Contacts/Cell/ContactTableCell.swift index 44cfa65b1..974963f4a 100644 --- a/fearless/Modules/Contacts/Cell/ContactTableCell.swift +++ b/fearless/Modules/Contacts/Cell/ContactTableCell.swift @@ -2,6 +2,7 @@ import UIKit protocol ContactTableCellDelegate: AnyObject { func didTapAddButton() + func didTapAccountScore() } class ContactTableCell: UITableViewCell { @@ -42,6 +43,12 @@ class ContactTableCell: UITableViewCell { return stackView }() + private let hStackView: UIStackView = { + let stackView = UIFactory.default.createHorizontalStackView() + stackView.distribution = .fill + return stackView + }() + let addButton: TriangularedButton = { let button = TriangularedButton() button.isHidden = false @@ -56,6 +63,12 @@ class ContactTableCell: UITableViewCell { return button }() + let accountScoreView: AccountScoreView = { + let view = AccountScoreView() + view.isHidden = true + return view + }() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -69,6 +82,7 @@ class ContactTableCell: UITableViewCell { } func bind(to viewModel: ContactTableCellModel) { + accountScoreView.bind(viewModel: viewModel.accountScoreViewModel) delegate = viewModel switch viewModel.contactType { case let .saved(contact): @@ -80,6 +94,10 @@ class ContactTableCell: UITableViewCell { addressLabel.text = address addButton.isHidden = false } + + accountScoreView.starView.didFinishTouchingCosmos = { [weak self] _ in + self?.delegate?.didTapAccountScore() + } } @objc private func addButtonClicked() { @@ -91,9 +109,20 @@ class ContactTableCell: UITableViewCell { contentView.addSubview(addButton) contentView.addSubview(labelsStackView) - labelsStackView.addArrangedSubview(nameLabel) + labelsStackView.addArrangedSubview(hStackView) labelsStackView.addArrangedSubview(addressLabel) + hStackView.addArrangedSubview(nameLabel) + hStackView.addArrangedSubview(accountScoreView) + + accountScoreView.snp.makeConstraints { make in + make.trailing.equalToSuperview() + } + + hStackView.snp.makeConstraints { make in + make.trailing.leading.equalToSuperview() + } + contactImageView.snp.makeConstraints { make in make.size.equalTo(LayoutConstants.contactImageViewSize) make.leading.equalToSuperview().offset(UIConstants.bigOffset) diff --git a/fearless/Modules/Contacts/ContactsAssembly.swift b/fearless/Modules/Contacts/ContactsAssembly.swift index 9f53e1dd9..1bd65fddd 100644 --- a/fearless/Modules/Contacts/ContactsAssembly.swift +++ b/fearless/Modules/Contacts/ContactsAssembly.swift @@ -2,6 +2,7 @@ import UIKit import SoraFoundation import RobinHood import SSFModels +import SSFNetwork enum ContactSource { case token(chainAsset: ChainAsset) @@ -57,11 +58,12 @@ enum ContactsAssembly { ) let router = ContactsRouter() + let accountScoreFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) let presenter = ContactsPresenter( interactor: interactor, router: router, localizationManager: localizationManager, - viewModelFactory: AddressBookViewModelFactory(), + viewModelFactory: AddressBookViewModelFactory(accountScoreFetcher: accountScoreFetcher), moduleOutput: moduleOutput, source: source, wallet: wallet diff --git a/fearless/Modules/Contacts/ContactsPresenter.swift b/fearless/Modules/Contacts/ContactsPresenter.swift index 48589c08a..db657b684 100644 --- a/fearless/Modules/Contacts/ContactsPresenter.swift +++ b/fearless/Modules/Contacts/ContactsPresenter.swift @@ -101,6 +101,10 @@ extension ContactsPresenter: ContactTableCellModelDelegate { view: view ) } + + func didTapAccountScore(address: String) { + router.presentAccountScore(address: address, from: view) + } } extension ContactsPresenter: CreateContactModuleOutput { diff --git a/fearless/Modules/Contacts/ContactsProtocols.swift b/fearless/Modules/Contacts/ContactsProtocols.swift index fc0bb55ee..335df731a 100644 --- a/fearless/Modules/Contacts/ContactsProtocols.swift +++ b/fearless/Modules/Contacts/ContactsProtocols.swift @@ -26,7 +26,7 @@ protocol ContactsInteractorOutput: AnyObject { func didReceiveError(_ error: Error) } -protocol ContactsRouterInput: PresentDismissable, ErrorPresentable, SheetAlertPresentable { +protocol ContactsRouterInput: PresentDismissable, ErrorPresentable, SheetAlertPresentable, AccountScorePresentable { func createContact( address: String?, chain: ChainModel, diff --git a/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift b/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift index 610195671..65f268afe 100644 --- a/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift +++ b/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift @@ -15,6 +15,12 @@ struct ContactsTableSectionModel { } final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { + private let accountScoreFetcher: AccountStatisticsFetching + + init(accountScoreFetcher: AccountStatisticsFetching) { + self.accountScoreFetcher = accountScoreFetcher + } + func buildCellViewModels( savedContacts: [Contact], recentContacts: [ContactType], @@ -22,9 +28,13 @@ final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { locale: Locale ) -> [ContactsTableSectionModel] { let recentContactsViewModels = recentContacts.map { contactType in - ContactTableCellModel( + + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: contactType.address) + + return ContactTableCellModel( contactType: contactType, - delegate: cellsDelegate + delegate: cellsDelegate, + accountScoreViewModel: accountScoreViewModel ) } let recentContactsSection = ContactsTableSectionModel( @@ -43,7 +53,9 @@ final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { contact.name.first?.lowercased() == firstLetter.lowercased() } let cellModels = contacts.map { contact in - ContactTableCellModel(contactType: .saved(contact), delegate: cellsDelegate) + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: contact.address) + + return ContactTableCellModel(contactType: .saved(contact), delegate: cellsDelegate, accountScoreViewModel: accountScoreViewModel) } return ContactsTableSectionModel(name: String(firstLetter), cellViewModels: cellModels) } diff --git a/fearless/Modules/Contacts/View Models/ContactsTableCellModel.swift b/fearless/Modules/Contacts/View Models/ContactsTableCellModel.swift index 9c5959e87..924259f32 100644 --- a/fearless/Modules/Contacts/View Models/ContactsTableCellModel.swift +++ b/fearless/Modules/Contacts/View Models/ContactsTableCellModel.swift @@ -2,6 +2,7 @@ import UIKit protocol ContactTableCellModelDelegate: AnyObject { func addContact(address: String) + func didTapAccountScore(address: String) } enum ContactType { @@ -20,11 +21,13 @@ enum ContactType { class ContactTableCellModel { let contactType: ContactType + let accountScoreViewModel: AccountScoreViewModel? weak var delegate: ContactTableCellModelDelegate? - init(contactType: ContactType, delegate: ContactTableCellModelDelegate?) { + init(contactType: ContactType, delegate: ContactTableCellModelDelegate?, accountScoreViewModel: AccountScoreViewModel?) { self.contactType = contactType self.delegate = delegate + self.accountScoreViewModel = accountScoreViewModel } } @@ -32,4 +35,8 @@ extension ContactTableCellModel: ContactTableCellDelegate { func didTapAddButton() { delegate?.addContact(address: contactType.address) } + + func didTapAccountScore() { + delegate?.didTapAccountScore(address: contactType.address) + } } diff --git a/fearless/Modules/NFT/NftSend/NftSendAssembly.swift b/fearless/Modules/NFT/NftSend/NftSendAssembly.swift index 38e584b51..feafe6412 100644 --- a/fearless/Modules/NFT/NftSend/NftSendAssembly.swift +++ b/fearless/Modules/NFT/NftSend/NftSendAssembly.swift @@ -5,6 +5,7 @@ import SoraKeystore import Web3 import RobinHood import SSFUtils +import SSFNetwork enum NftSendAssemblyError: Error { case substrateNftNotImplemented @@ -44,10 +45,13 @@ enum NftSendAssembly { logger: Logger.shared ) let accountInfoSubscriptionAdapter = AccountInfoSubscriptionAdapter(walletLocalSubscriptionFactory: walletLocalSubscriptionFactory, selectedMetaAccount: wallet) + + let accountStatisticsFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) + let scamInfoFetcher = ScamInfoFetcher(scamServiceOperationFactory: scamServiceOperationFactory, accountScoreFetching: accountStatisticsFetcher) let interactor = NftSendInteractor( transferService: transferService, operationManager: OperationManagerFacade.sharedManager, - scamServiceOperationFactory: scamServiceOperationFactory, + scamInfoFetching: scamInfoFetcher, addressChainDefiner: addressChainDefiner, accountInfoSubscriptionAdapter: accountInfoSubscriptionAdapter, priceLocalSubscriber: PriceLocalStorageSubscriberImpl.shared, diff --git a/fearless/Modules/NFT/NftSend/NftSendInteractor.swift b/fearless/Modules/NFT/NftSend/NftSendInteractor.swift index 6c499c261..cbe846e45 100644 --- a/fearless/Modules/NFT/NftSend/NftSendInteractor.swift +++ b/fearless/Modules/NFT/NftSend/NftSendInteractor.swift @@ -9,7 +9,7 @@ final class NftSendInteractor { private weak var output: NftSendInteractorOutput? private let transferService: NftTransferService private let operationManager: OperationManagerProtocol - private let scamServiceOperationFactory: ScamServiceOperationFactoryProtocol + private let scamInfoFetching: ScamInfoFetching private let addressChainDefiner: AddressChainDefiner private let accountInfoSubscriptionAdapter: AccountInfoSubscriptionAdapterProtocol private let priceLocalSubscriber: PriceLocalStorageSubscriber @@ -21,7 +21,7 @@ final class NftSendInteractor { init( transferService: NftTransferService, operationManager: OperationManagerProtocol, - scamServiceOperationFactory: ScamServiceOperationFactoryProtocol, + scamInfoFetching: ScamInfoFetching, addressChainDefiner: AddressChainDefiner, accountInfoSubscriptionAdapter: AccountInfoSubscriptionAdapterProtocol, priceLocalSubscriber: PriceLocalStorageSubscriber, @@ -30,7 +30,7 @@ final class NftSendInteractor { ) { self.transferService = transferService self.operationManager = operationManager - self.scamServiceOperationFactory = scamServiceOperationFactory + self.scamInfoFetching = scamInfoFetching self.addressChainDefiner = addressChainDefiner self.accountInfoSubscriptionAdapter = accountInfoSubscriptionAdapter self.priceLocalSubscriber = priceLocalSubscriber @@ -91,23 +91,10 @@ extension NftSendInteractor: NftSendInteractorInput { } func fetchScamInfo(for address: String) { - let allOperation = scamServiceOperationFactory.fetchScamInfoOperation(for: address) - - allOperation.completionBlock = { [weak self] in - guard let result = allOperation.result else { - return - } - - switch result { - case let .success(scamInfo): - DispatchQueue.main.async { - self?.output?.didReceive(scamInfo: scamInfo) - } - case .failure: - break - } + Task { + let scamInfo = try await scamInfoFetching.fetch(address: address) + output?.didReceive(scamInfo: scamInfo) } - operationManager.enqueue(operations: [allOperation], in: .transient) } func validate(address: String?, for chain: ChainModel) -> AddressValidationResult { diff --git a/fearless/Modules/Profile/ProfilePresenter.swift b/fearless/Modules/Profile/ProfilePresenter.swift index c2c253355..9a1c62742 100644 --- a/fearless/Modules/Profile/ProfilePresenter.swift +++ b/fearless/Modules/Profile/ProfilePresenter.swift @@ -142,6 +142,10 @@ extension ProfilePresenter: ProfilePresenterProtocol { wireframe.present(viewModel: viewModel, from: view) } + + func didTapAccountScore(address: String) { + wireframe.presentAccountScore(address: address, from: view) + } } extension ProfilePresenter: CheckPincodeModuleOutput { diff --git a/fearless/Modules/Profile/ProfileProtocol.swift b/fearless/Modules/Profile/ProfileProtocol.swift index c4d372077..418c5bc4c 100644 --- a/fearless/Modules/Profile/ProfileProtocol.swift +++ b/fearless/Modules/Profile/ProfileProtocol.swift @@ -11,6 +11,7 @@ protocol ProfilePresenterProtocol: AnyObject { func activateOption(_ option: ProfileOption) func logout() func switcherValueChanged(isOn: Bool, index: Int) + func didTapAccountScore(address: String) } protocol ProfileInteractorInputProtocol: AnyObject { @@ -33,7 +34,7 @@ protocol ProfileWireframeProtocol: ErrorPresentable, WebPresentable, ModalAlertPresenting, AddressOptionsPresentable, - AccountManagementPresentable { + AccountManagementPresentable, AccountScorePresentable { func showAccountDetails( from view: ProfileViewProtocol?, metaAccount: MetaAccountModel diff --git a/fearless/Modules/Profile/ProfileViewController.swift b/fearless/Modules/Profile/ProfileViewController.swift index 3d94ab364..0357cdf2c 100644 --- a/fearless/Modules/Profile/ProfileViewController.swift +++ b/fearless/Modules/Profile/ProfileViewController.swift @@ -251,4 +251,8 @@ extension ProfileViewController: WalletsManagmentTableCellDelegate { func didTapOptionsCell(with _: IndexPath?) { presenter.activateAccountDetails() } + + func didTapAccountScore(address: String) { + presenter.didTapAccountScore(address: address) + } } diff --git a/fearless/Modules/Profile/ProfileViewFactory.swift b/fearless/Modules/Profile/ProfileViewFactory.swift index 21dcfd161..4529d1b0f 100644 --- a/fearless/Modules/Profile/ProfileViewFactory.swift +++ b/fearless/Modules/Profile/ProfileViewFactory.swift @@ -4,6 +4,7 @@ import SoraKeystore import IrohaCrypto import SSFUtils import RobinHood +import SSFNetwork final class ProfileViewFactory: ProfileViewFactoryProtocol { static func createView() -> ProfileViewProtocol? { @@ -14,11 +15,17 @@ final class ProfileViewFactory: ProfileViewFactoryProtocol { for: nil, sortDescriptors: [NSSortDescriptor.accountsByOrder] ) + + let accountScoreFetcher = NomisAccountStatisticsFetcher( + networkWorker: NetworkWorkerImpl(), + signer: NomisRequestSigner() + ) let settings = SettingsManager.shared let profileViewModelFactory = ProfileViewModelFactory( iconGenerator: UniversalIconGenerator(), biometry: BiometryAuth(), - settings: settings + settings: settings, + accountScoreFetcher: accountScoreFetcher ) let accountRepositoryFactory = AccountRepositoryFactory(storageFacade: UserDataStorageFacade.shared) diff --git a/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift b/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift index d641eac2f..67f7bbe63 100644 --- a/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift +++ b/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift @@ -34,17 +34,20 @@ final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { private let biometry: BiometryAuthProtocol private let settings: SettingsManagerProtocol private lazy var assetBalanceFormatterFactory = AssetBalanceFormatterFactory() + private let accountScoreFetcher: AccountStatisticsFetching // MARK: - Constructors init( iconGenerator: IconGenerating, biometry: BiometryAuthProtocol, - settings: SettingsManagerProtocol + settings: SettingsManagerProtocol, + accountScoreFetcher: AccountStatisticsFetching ) { self.iconGenerator = iconGenerator self.biometry = biometry self.settings = settings + self.accountScoreFetcher = accountScoreFetcher } // MARK: - Public methods @@ -104,11 +107,15 @@ final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { ) } + let address = wallet.ethereumAddress?.toHex(includePrefix: true) + let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0) } + return WalletsManagmentCellViewModel( isSelected: false, walletName: wallet.name, fiatBalance: fiatBalance, - dayChange: dayChange + dayChange: dayChange, + accountScoreViewModel: accountScoreViewModel ) } diff --git a/fearless/Modules/Send/SendAssembly.swift b/fearless/Modules/Send/SendAssembly.swift index ee644452f..f34d5cd71 100644 --- a/fearless/Modules/Send/SendAssembly.swift +++ b/fearless/Modules/Send/SendAssembly.swift @@ -55,6 +55,8 @@ final class SendAssembly { ) let runtimeMetadataRepository: AsyncCoreDataRepositoryDefault = SubstrateDataStorageFacade.shared.createAsyncRepository() + let accountStatisticsFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) + let scamInfoFetcher = ScamInfoFetcher(scamServiceOperationFactory: scamServiceOperationFactory, accountScoreFetching: accountStatisticsFetcher) let interactor = SendInteractor( accountInfoSubscriptionAdapter: AccountInfoSubscriptionAdapter( walletLocalSubscriptionFactory: WalletLocalSubscriptionFactory.shared, @@ -62,7 +64,7 @@ final class SendAssembly { ), priceLocalSubscriber: priceLocalSubscriber, operationManager: operationManager, - scamServiceOperationFactory: scamServiceOperationFactory, + scamInfoFetching: scamInfoFetcher, chainAssetFetching: chainAssetFetching, dependencyContainer: dependencyContainer, addressChainDefiner: addressChainDefiner, diff --git a/fearless/Modules/Send/SendInteractor.swift b/fearless/Modules/Send/SendInteractor.swift index 762c155ae..76c6ca1b1 100644 --- a/fearless/Modules/Send/SendInteractor.swift +++ b/fearless/Modules/Send/SendInteractor.swift @@ -12,7 +12,7 @@ final class SendInteractor: RuntimeConstantFetching { private let priceLocalSubscriber: PriceLocalStorageSubscriber private let accountInfoSubscriptionAdapter: AccountInfoSubscriptionAdapterProtocol private let operationManager: OperationManagerProtocol - private let scamServiceOperationFactory: ScamServiceOperationFactoryProtocol + private let scamInfoFetching: ScamInfoFetching private let chainAssetFetching: ChainAssetFetchingProtocol private let addressChainDefiner: AddressChainDefiner private var equilibriumTotalBalanceService: EquilibriumTotalBalanceServiceProtocol? @@ -30,7 +30,7 @@ final class SendInteractor: RuntimeConstantFetching { accountInfoSubscriptionAdapter: AccountInfoSubscriptionAdapterProtocol, priceLocalSubscriber: PriceLocalStorageSubscriber, operationManager: OperationManagerProtocol, - scamServiceOperationFactory: ScamServiceOperationFactoryProtocol, + scamInfoFetching: ScamInfoFetching, chainAssetFetching: ChainAssetFetchingProtocol, dependencyContainer: SendDepencyContainer, addressChainDefiner: AddressChainDefiner, @@ -39,7 +39,7 @@ final class SendInteractor: RuntimeConstantFetching { self.accountInfoSubscriptionAdapter = accountInfoSubscriptionAdapter self.priceLocalSubscriber = priceLocalSubscriber self.operationManager = operationManager - self.scamServiceOperationFactory = scamServiceOperationFactory + self.scamInfoFetching = scamInfoFetching self.chainAssetFetching = chainAssetFetching self.dependencyContainer = dependencyContainer self.addressChainDefiner = addressChainDefiner @@ -176,23 +176,10 @@ extension SendInteractor: SendInteractorInput { } func fetchScamInfo(for address: String) { - let allOperation = scamServiceOperationFactory.fetchScamInfoOperation(for: address) - - allOperation.completionBlock = { [weak self] in - guard let result = allOperation.result else { - return - } - - switch result { - case let .success(scamInfo): - DispatchQueue.main.async { - self?.output?.didReceive(scamInfo: scamInfo) - } - case .failure: - break - } + Task { + let scamInfo = try await scamInfoFetching.fetch(address: address) + output?.didReceive(scamInfo: scamInfo) } - operationManager.enqueue(operations: [allOperation], in: .transient) } func getFeePaymentChainAsset(for chainAsset: ChainAsset?) -> ChainAsset? { diff --git a/fearless/Modules/Send/SendPresenter.swift b/fearless/Modules/Send/SendPresenter.swift index 5087f302c..03e6b49a5 100644 --- a/fearless/Modules/Send/SendPresenter.swift +++ b/fearless/Modules/Send/SendPresenter.swift @@ -1035,7 +1035,10 @@ extension SendPresenter: SendInteractorOutput { func didReceive(scamInfo: ScamInfo?) { self.scamInfo = scamInfo - view?.didReceive(scamInfo: scamInfo) + + DispatchQueue.main.async { [weak self] in + self?.view?.didReceive(scamInfo: scamInfo) + } } func didReceiveAccountInfo(result: Result, for chainAsset: ChainAsset) { diff --git a/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift b/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift index 4edb595da..7e9ff9f47 100644 --- a/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift +++ b/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift @@ -3,6 +3,7 @@ import WalletConnectSign import SoraFoundation import SoraUI import RobinHood +import SSFNetwork enum WalletConnectSessionAssembly { static func configureModule( @@ -24,9 +25,9 @@ enum WalletConnectSessionAssembly { storageFacade: UserDataStorageFacade.shared ) - let accountInfoRepository = substrateRepositoryFactory.createAccountInfoStorageItemRepository() let walletBalanceSubscriptionAdapter = WalletBalanceSubscriptionAdapter.shared + let accountScoreFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) let interactor = WalletConnectSessionInteractor( walletConnect: WalletConnectServiceImpl.shared, walletBalanceSubscriptionAdapter: walletBalanceSubscriptionAdapter, @@ -43,7 +44,8 @@ enum WalletConnectSessionAssembly { session: session, walletConnectModelFactory: walletConnectModelFactory, walletConnectPayloaFactory: walletConnectPayloaFactory, - assetBalanceFormatterFactory: AssetBalanceFormatterFactory() + assetBalanceFormatterFactory: AssetBalanceFormatterFactory(), + accountScoreFetcher: accountScoreFetcher ) let presenter = WalletConnectSessionPresenter( request: request, diff --git a/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift b/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift index ac4959efd..36e614f5a 100644 --- a/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift +++ b/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift @@ -19,19 +19,22 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo private let walletConnectModelFactory: WalletConnectModelFactory private let walletConnectPayloaFactory: WalletConnectPayloadFactory private let assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol + private let accountScoreFetcher: AccountStatisticsFetching init( request: Request, session: Session?, walletConnectModelFactory: WalletConnectModelFactory, walletConnectPayloaFactory: WalletConnectPayloadFactory, - assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol + assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol, + accountScoreFetcher: AccountStatisticsFetching ) { self.request = request self.session = session self.walletConnectModelFactory = walletConnectModelFactory self.walletConnectPayloaFactory = walletConnectPayloaFactory self.assetBalanceFormatterFactory = assetBalanceFormatterFactory + self.accountScoreFetcher = accountScoreFetcher } func buildViewModel( @@ -102,12 +105,16 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo balanceInfo: WalletBalanceInfos?, locale: Locale ) -> WalletsManagmentCellViewModel { + let address = wallet.ethereumAddress?.toHex(includePrefix: true) + let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0) } + guard let balance = balanceInfo?[wallet.metaId] else { return WalletsManagmentCellViewModel( isSelected: false, walletName: wallet.name, fiatBalance: nil, - dayChange: nil + dayChange: nil, + accountScoreViewModel: accountScoreViewModel ) } let balanceTokenFormatterValue = tokenFormatter( @@ -129,7 +136,8 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo isSelected: false, walletName: wallet.name, fiatBalance: totalFiatValue, - dayChange: dayChange + dayChange: dayChange, + accountScoreViewModel: accountScoreViewModel ) return viewModel diff --git a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModel.swift b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModel.swift index ccef9aab1..7cdf361f0 100644 --- a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModel.swift +++ b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModel.swift @@ -5,4 +5,5 @@ struct WalletMainContainerViewModel { let selectedFilter: String let selectedFilterImage: ImageViewModelProtocol? let address: String? + let accountScoreViewModel: AccountScoreViewModel? } diff --git a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift index b5c4f9d35..f44a2b7a7 100644 --- a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift +++ b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift @@ -11,6 +11,12 @@ protocol WalletMainContainerViewModelFactoryProtocol { } final class WalletMainContainerViewModelFactory: WalletMainContainerViewModelFactoryProtocol { + private let accountScoreFetcher: AccountStatisticsFetching + + init(accountScoreFetcher: AccountStatisticsFetching) { + self.accountScoreFetcher = accountScoreFetcher + } + func buildViewModel( selectedFilter: NetworkManagmentFilter, selectedChains: [ChainModel], @@ -46,11 +52,15 @@ final class WalletMainContainerViewModelFactory: WalletMainContainerViewModelFac address = address1 } + let ethAddress = selectedMetaAccount.ethereumAddress?.toHex(includePrefix: true) + let accountScoreViewModel = ethAddress.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0) } + return WalletMainContainerViewModel( walletName: selectedMetaAccount.name, selectedFilter: selectedFilterName, selectedFilterImage: selectedFilterImage, - address: address + address: address, + accountScoreViewModel: accountScoreViewModel ) } } diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift index 425f6ba72..1638939ab 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift @@ -50,7 +50,11 @@ final class WalletMainContainerAssembly { walletRepository: AnyDataProviderRepository(accountRepository), stashItemRepository: substrateRepositoryFactory.createStashItemRepository() ) - let accountStatisticsFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) + let accountScoreFetcher = NomisAccountStatisticsFetcher( + networkWorker: NetworkWorkerImpl(), + signer: NomisRequestSigner() + ) + let interactor = WalletMainContainerInteractor( accountRepository: AnyDataProviderRepository(accountRepository), chainRepository: AnyDataProviderRepository(chainRepository), @@ -59,8 +63,7 @@ final class WalletMainContainerAssembly { eventCenter: EventCenter.shared, deprecatedAccountsCheckService: deprecatedAccountsCheckService, applicationHandler: ApplicationHandler(), - walletConnectService: walletConnect, - accountStatisticsFetcher: accountStatisticsFetcher + walletConnectService: walletConnect ) let router = WalletMainContainerRouter() @@ -73,16 +76,16 @@ final class WalletMainContainerAssembly { return nil } + let viewModelFactory = WalletMainContainerViewModelFactory(accountScoreFetcher: accountScoreFetcher) let presenter = WalletMainContainerPresenter( balanceInfoModuleInput: balanceInfoModule.input, assetListModuleInput: assetListModule.input, nftModuleInput: nftModule.input, wallet: wallet, - viewModelFactory: WalletMainContainerViewModelFactory(), + viewModelFactory: viewModelFactory, interactor: interactor, router: router, - localizationManager: localizationManager, - accountScoreViewModelFactory: AccountScoreViewModelFactoryImpl() + localizationManager: localizationManager ) let view = WalletMainContainerViewController( diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift index 8a76f6b56..ee2472a69 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerInteractor.swift @@ -16,7 +16,6 @@ final class WalletMainContainerInteractor { private let deprecatedAccountsCheckService: DeprecatedControllerStashAccountCheckServiceProtocol private let applicationHandler: ApplicationHandler private let walletConnectService: WalletConnectService - private let accountStatisticsFetcher: AccountStatisticsFetching // MARK: - Constructor @@ -28,8 +27,7 @@ final class WalletMainContainerInteractor { eventCenter: EventCenterProtocol, deprecatedAccountsCheckService: DeprecatedControllerStashAccountCheckServiceProtocol, applicationHandler: ApplicationHandler, - walletConnectService: WalletConnectService, - accountStatisticsFetcher: AccountStatisticsFetching + walletConnectService: WalletConnectService ) { self.wallet = wallet self.chainRepository = chainRepository @@ -39,34 +37,10 @@ final class WalletMainContainerInteractor { self.deprecatedAccountsCheckService = deprecatedAccountsCheckService self.applicationHandler = applicationHandler self.walletConnectService = walletConnectService - self.accountStatisticsFetcher = accountStatisticsFetcher applicationHandler.delegate = self } - // MARK: - Private methods - - private func fetchAccountStats() { - guard let ethereumAccountId = wallet.ethereumAddress else { - return - } - - let address = ethereumAccountId.toHex(includePrefix: true) - - Task { - do { - let stream = try await accountStatisticsFetcher.subscribeForStatistics(address: address, cacheOptions: .onAll) - for try await statistics in stream { - if let stats = statistics.value?.data { - DispatchQueue.main.async { [weak self] in - self?.output?.didReceiveAccountStatistics(stats) - } - } - } - } catch { - print("Account statistics fetching error: ", error) - } - } - } + // MARK: - Private method private func fetchNetworkManagmentFilter() { guard let identifier = wallet.networkManagmentFilter else { @@ -134,7 +108,6 @@ extension WalletMainContainerInteractor: WalletMainContainerInteractorInput { self.output = output eventCenter.add(observer: self, dispatchIn: .main) fetchNetworkManagmentFilter() - fetchAccountStats() } func walletConnect(uri: String) async throws { diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift index ad255f1f6..f5d0377fc 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift @@ -13,7 +13,6 @@ final class WalletMainContainerPresenter { private weak var view: WalletMainContainerViewInput? private let router: WalletMainContainerRouterInput private let interactor: WalletMainContainerInteractorInput - private let accountScoreViewModelFactory: AccountScoreViewModelFactory private var wallet: MetaAccountModel private let viewModelFactory: WalletMainContainerViewModelFactoryProtocol @@ -33,8 +32,7 @@ final class WalletMainContainerPresenter { viewModelFactory: WalletMainContainerViewModelFactoryProtocol, interactor: WalletMainContainerInteractorInput, router: WalletMainContainerRouterInput, - localizationManager: LocalizationManagerProtocol, - accountScoreViewModelFactory: AccountScoreViewModelFactory + localizationManager: LocalizationManagerProtocol ) { self.balanceInfoModuleInput = balanceInfoModuleInput self.assetListModuleInput = assetListModuleInput @@ -43,7 +41,6 @@ final class WalletMainContainerPresenter { self.viewModelFactory = viewModelFactory self.interactor = interactor self.router = router - self.accountScoreViewModelFactory = accountScoreViewModelFactory self.localizationManager = localizationManager } @@ -117,6 +114,14 @@ extension WalletMainContainerPresenter: WalletMainContainerViewOutput { wallet: wallet ) } + + func didTapAccountScore() { + guard let address = wallet.ethereumAddress?.toHex(includePrefix: true) else { + return + } + + router.presentAccountScore(address: address, from: view) + } } // MARK: - WalletMainContainerInteractorOutput @@ -221,14 +226,6 @@ extension WalletMainContainerPresenter: WalletMainContainerInteractorOutput { actions: [action] ) } - - func didReceiveAccountStatistics(_ accountStatistics: AccountStatistics) { - if let viewModel = accountScoreViewModelFactory.buildViewModel(from: accountStatistics) { - view?.didReceiveAccountScoreViewModel(viewModel) - } - } - - func didReceiveAccountStatisticsError(_: Error) {} } // MARK: - Localizable diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift index 8f9a15e2f..9299b5068 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerProtocols.swift @@ -7,7 +7,6 @@ typealias WalletMainContainerModuleCreationResult = ( protocol WalletMainContainerViewInput: ControllerBackedProtocol, HiddableBarWhenPushed { func didReceiveViewModel(_ viewModel: WalletMainContainerViewModel) - func didReceiveAccountScoreViewModel(_ viewModel: AccountScoreViewModel) } protocol WalletMainContainerViewOutput: AnyObject { @@ -18,6 +17,7 @@ protocol WalletMainContainerViewOutput: AnyObject { func didTapSelectNetwork() func didTapOnBalance() func addressDidCopied() + func didTapAccountScore() } protocol WalletMainContainerInteractorInput: AnyObject { @@ -31,11 +31,9 @@ protocol WalletMainContainerInteractorOutput: AnyObject { func didReceiveError(_ error: Error) func didReceiveControllerAccountIssue(issue: ControllerAccountIssue, hasStashItem: Bool) func didReceiveStashAccountIssue(address: String) - func didReceiveAccountStatistics(_ accountStatistics: AccountStatistics) - func didReceiveAccountStatisticsError(_ error: Error) } -protocol WalletMainContainerRouterInput: SheetAlertPresentable, ErrorPresentable, ApplicationStatusPresentable, AccountManagementPresentable { +protocol WalletMainContainerRouterInput: SheetAlertPresentable, ErrorPresentable, ApplicationStatusPresentable, AccountManagementPresentable, AccountScorePresentable { func showWalletManagment( from view: WalletMainContainerViewInput?, moduleOutput: WalletsManagmentModuleOutput? diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift index 20544fd27..6fefb7679 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift @@ -10,6 +10,7 @@ final class WalletMainContainerViewController: UIViewController, ViewHolder, All private let balanceInfoViewController: UIViewController private let pageControllers: [UIViewController] + var accountScoreTapGesture: UITapGestureRecognizer? // MARK: - Constructor @@ -80,6 +81,10 @@ final class WalletMainContainerViewController: UIViewController, ViewHolder, All let walletBalanceTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBalanceDidTap)) rootView.walletBalanceViewContainer.addGestureRecognizer(walletBalanceTapGesture) + + rootView.accountScoreView.starView.didFinishTouchingCosmos = { [weak self] _ in + self?.output.didTapAccountScore() + } } // MARK: - Actions diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift index 495660235..a0e49eb68 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift @@ -113,6 +113,8 @@ final class WalletMainContainerViewLayout: UIView { } else { addressCopyableLabel.isHidden = true } + + accountScoreView.bind(viewModel: viewModel.accountScoreViewModel) } func addBalance(_ view: UIView) { @@ -164,12 +166,6 @@ final class WalletMainContainerViewLayout: UIView { make.size.equalTo(Constants.walletIconSize) } - navigationContainerView.addSubview(accountScoreView) - accountScoreView.snp.makeConstraints { make in - make.top.equalTo(switchWalletButton.snp.bottom).offset(4) - make.centerX.equalTo(switchWalletButton.snp.centerX) - } - let walletInfoVStackView = UIFactory.default.createVerticalStackView(spacing: 6) walletInfoVStackView.alignment = .center walletInfoVStackView.distribution = .fill @@ -213,6 +209,12 @@ final class WalletMainContainerViewLayout: UIView { } private func setupWalletBalanceLayout() { + addSubview(accountScoreView) + accountScoreView.snp.makeConstraints { make in + make.top.equalTo(navigationContainerView.snp.bottom).offset(4) + make.centerX.equalTo(switchWalletButton.snp.centerX) + } + addressCopyableLabel.snp.makeConstraints { make in make.width.lessThanOrEqualTo(200) make.height.equalTo(24) diff --git a/fearless/Modules/WalletOption/WalletOptionPresenter.swift b/fearless/Modules/WalletOption/WalletOptionPresenter.swift index fa7feabae..239f7924b 100644 --- a/fearless/Modules/WalletOption/WalletOptionPresenter.swift +++ b/fearless/Modules/WalletOption/WalletOptionPresenter.swift @@ -77,6 +77,14 @@ extension WalletOptionPresenter: WalletOptionViewOutput { askAndPerformRemoveWallet() } + func accountScoreDidTap() { + guard let address = wallet.info.ethereumAddress?.toHex(includePrefix: true) else { + return + } + + router.presentAccountScore(address: address, from: view) + } + func didLoad(view: WalletOptionViewInput) { self.view = view interactor.setup(with: self) diff --git a/fearless/Modules/WalletOption/WalletOptionProtocols.swift b/fearless/Modules/WalletOption/WalletOptionProtocols.swift index a80a1d0ac..2f415bf61 100644 --- a/fearless/Modules/WalletOption/WalletOptionProtocols.swift +++ b/fearless/Modules/WalletOption/WalletOptionProtocols.swift @@ -10,6 +10,7 @@ protocol WalletOptionViewOutput: AnyObject { func exportWalletDidTap() func deleteWalletDidTap() func changeWalletNameDidTap() + func accountScoreDidTap() } protocol WalletOptionInteractorInput: AnyObject { @@ -22,7 +23,7 @@ protocol WalletOptionInteractorOutput: AnyObject { func walletRemoved() } -protocol WalletOptionRouterInput: SheetAlertPresentable, AnyDismissable { +protocol WalletOptionRouterInput: SheetAlertPresentable, AnyDismissable, AccountScorePresentable { func showWalletDetails( from view: ControllerBackedProtocol?, for wallet: MetaAccountModel diff --git a/fearless/Modules/WalletOption/WalletOptionViewController.swift b/fearless/Modules/WalletOption/WalletOptionViewController.swift index 31013cc5a..27c051ce5 100644 --- a/fearless/Modules/WalletOption/WalletOptionViewController.swift +++ b/fearless/Modules/WalletOption/WalletOptionViewController.swift @@ -51,6 +51,9 @@ final class WalletOptionViewController: UIViewController, ViewHolder { rootView.changeWalletNameButton.addAction { [weak self] in self?.output.changeWalletNameDidTap() } + rootView.accountScoreButton.addAction { [weak self] in + self?.output.accountScoreDidTap() + } } } diff --git a/fearless/Modules/WalletOption/WalletOptionViewLayout.swift b/fearless/Modules/WalletOption/WalletOptionViewLayout.swift index 206651928..712db25a5 100644 --- a/fearless/Modules/WalletOption/WalletOptionViewLayout.swift +++ b/fearless/Modules/WalletOption/WalletOptionViewLayout.swift @@ -37,6 +37,14 @@ final class WalletOptionViewLayout: UIView { return button }() + let accountScoreButton: TriangularedButton = { + let button = TriangularedButton() + button.triangularedView?.fillColor = R.color.colorBlack1()! + button.triangularedView?.shadowOpacity = 0 + button.imageWithTitleView?.titleFont = .h4Title + return button + }() + let deleteWalletButton: TriangularedButton = { let button = TriangularedButton() button.triangularedView?.fillColor = R.color.colorBlack1()! @@ -57,6 +65,7 @@ final class WalletOptionViewLayout: UIView { backupWalletButton, walletDetailsButton, changeWalletNameButton, + accountScoreButton, deleteWalletButton ] }() @@ -87,6 +96,7 @@ final class WalletOptionViewLayout: UIView { deleteWalletButton.imageWithTitleView?.title = R.string.localizable.walletOptionsDelete( preferredLanguages: locale.rLanguages ) + accountScoreButton.imageWithTitleView?.title = R.string.localizable.accountStatsWalletOptionTitle(preferredLanguages: locale.rLanguages) } private func setupLayout() { @@ -130,9 +140,8 @@ final class WalletOptionViewLayout: UIView { make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom).inset(UIConstants.accessoryItemsSpacing) } - vStackView.addArrangedSubview(backupWalletButton) - vStackView.addArrangedSubview(walletDetailsButton) - vStackView.addArrangedSubview(changeWalletNameButton) - vStackView.addArrangedSubview(deleteWalletButton) + buttons.forEach { + vStackView.addArrangedSubview($0) + } } } diff --git a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentCellViewModel.swift b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentCellViewModel.swift index 27939b806..7f4ebc64a 100644 --- a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentCellViewModel.swift +++ b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentCellViewModel.swift @@ -5,4 +5,5 @@ struct WalletsManagmentCellViewModel { let walletName: String let fiatBalance: String? let dayChange: NSAttributedString? + let accountScoreViewModel: AccountScoreViewModel? } diff --git a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift index ac42c35be..78efdbe3d 100644 --- a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift +++ b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift @@ -13,9 +13,14 @@ protocol WalletsManagmentViewModelFactoryProtocol { final class WalletsManagmentViewModelFactory: WalletsManagmentViewModelFactoryProtocol { private let assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol + private let accountScoreFetcher: AccountStatisticsFetching - init(assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol) { + init( + assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol, + accountScoreFetcher: AccountStatisticsFetching + ) { self.assetBalanceFormatterFactory = assetBalanceFormatterFactory + self.accountScoreFetcher = accountScoreFetcher } func buildViewModel( @@ -35,12 +40,16 @@ final class WalletsManagmentViewModelFactory: WalletsManagmentViewModelFactoryPr isSelected = selectedWalletId == nil ? false : managedMetaAccount.info.metaId == selectedWalletId } + let address = managedMetaAccount.info.ethereumAddress?.toHex(includePrefix: true) + let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0) } + guard let walletBalance = balances[key] else { return WalletsManagmentCellViewModel( isSelected: isSelected, walletName: managedMetaAccount.info.name, fiatBalance: nil, - dayChange: nil + dayChange: nil, + accountScoreViewModel: accountScoreViewModel ) } @@ -58,7 +67,8 @@ final class WalletsManagmentViewModelFactory: WalletsManagmentViewModelFactoryPr isSelected: isSelected, walletName: managedMetaAccount.info.name, fiatBalance: fiatBalance, - dayChange: nil + dayChange: nil, + accountScoreViewModel: accountScoreViewModel ) } @@ -73,7 +83,8 @@ final class WalletsManagmentViewModelFactory: WalletsManagmentViewModelFactoryPr isSelected: isSelected, walletName: managedMetaAccount.info.name, fiatBalance: totalFiatValue, - dayChange: dayChange + dayChange: dayChange, + accountScoreViewModel: accountScoreViewModel ) return viewModel } diff --git a/fearless/Modules/WalletsManagment/WalletsManagmentAssembly.swift b/fearless/Modules/WalletsManagment/WalletsManagmentAssembly.swift index 5c1b3672e..4c3907b3e 100644 --- a/fearless/Modules/WalletsManagment/WalletsManagmentAssembly.swift +++ b/fearless/Modules/WalletsManagment/WalletsManagmentAssembly.swift @@ -23,18 +23,11 @@ final class WalletsManagmentAssembly { sortDescriptors: [] ) - let priceLocalSubscriber = PriceLocalStorageSubscriberImpl.shared - let chainRepository = ChainRepositoryFactory().createRepository( for: NSPredicate.enabledCHain(), sortDescriptors: [NSSortDescriptor.chainsByAddressPrefix] ) - let chainAssetFetching = ChainAssetsFetching( - chainRepository: AnyDataProviderRepository(chainRepository), - operationQueue: sharedDefaultQueue - ) - let walletBalanceSubscriptionAdapter = WalletBalanceSubscriptionAdapter.shared let featureToggleProvider = FeatureToggleProvider( @@ -53,9 +46,14 @@ final class WalletsManagmentAssembly { ) let router = WalletsManagmentRouter() + let accountScoreFetcher = NomisAccountStatisticsFetcher( + networkWorker: NetworkWorkerImpl(), + signer: NomisRequestSigner() + ) let assetBalanceFormatterFactory = AssetBalanceFormatterFactory() let viewModelFactory = WalletsManagmentViewModelFactory( - assetBalanceFormatterFactory: assetBalanceFormatterFactory + assetBalanceFormatterFactory: assetBalanceFormatterFactory, + accountScoreFetcher: accountScoreFetcher ) let presenter = WalletsManagmentPresenter( diff --git a/fearless/Modules/WalletsManagment/WalletsManagmentPresenter.swift b/fearless/Modules/WalletsManagment/WalletsManagmentPresenter.swift index 468bd135b..6346c17be 100644 --- a/fearless/Modules/WalletsManagment/WalletsManagmentPresenter.swift +++ b/fearless/Modules/WalletsManagment/WalletsManagmentPresenter.swift @@ -182,6 +182,10 @@ extension WalletsManagmentPresenter: WalletsManagmentViewOutput { self.view = view interactor.setup(with: self) } + + func didTapAccountScore(address: String) { + router.presentAccountScore(address: address, from: view) + } } // MARK: - WalletsManagmentInteractorOutput diff --git a/fearless/Modules/WalletsManagment/WalletsManagmentProtocols.swift b/fearless/Modules/WalletsManagment/WalletsManagmentProtocols.swift index 3ae59d138..e2bab627f 100644 --- a/fearless/Modules/WalletsManagment/WalletsManagmentProtocols.swift +++ b/fearless/Modules/WalletsManagment/WalletsManagmentProtocols.swift @@ -12,6 +12,7 @@ protocol WalletsManagmentViewOutput: AnyObject { func didTapOptions(for indexPath: IndexPath) func didTapClose() func didTap(on indexPath: IndexPath) + func didTapAccountScore(address: String) } protocol WalletsManagmentInteractorInput: AnyObject { @@ -28,7 +29,7 @@ protocol WalletsManagmentInteractorOutput: AnyObject { func didReceiveFeatureToggleConfig(result: Result?) } -protocol WalletsManagmentRouterInput: SheetAlertPresentable, ErrorPresentable { +protocol WalletsManagmentRouterInput: SheetAlertPresentable, ErrorPresentable, AccountScorePresentable { func showOptions( from view: WalletsManagmentViewInput?, metaAccount: ManagedMetaAccountModel, diff --git a/fearless/Modules/WalletsManagment/WalletsManagmentTableCell.swift b/fearless/Modules/WalletsManagment/WalletsManagmentTableCell.swift index 27b5bb888..de125f2aa 100644 --- a/fearless/Modules/WalletsManagment/WalletsManagmentTableCell.swift +++ b/fearless/Modules/WalletsManagment/WalletsManagmentTableCell.swift @@ -4,6 +4,7 @@ import SoraUI protocol WalletsManagmentTableCellDelegate: AnyObject { func didTapOptionsCell(with indexPath: IndexPath?) + func didTapAccountScore(address: String) } final class WalletsManagmentTableCell: UITableViewCell { @@ -59,6 +60,8 @@ final class WalletsManagmentTableCell: UITableViewCell { return button }() + private let accountScoreView = AccountScoreView() + private var skeletonView: SkrullableView? weak var delegate: WalletsManagmentTableCellDelegate? { @@ -96,6 +99,14 @@ final class WalletsManagmentTableCell: UITableViewCell { } else { stopLoadingIfNeeded() } + + accountScoreView.bind(viewModel: viewModel.accountScoreViewModel) + + accountScoreView.starView.didFinishTouchingCosmos = { [weak self] _ in + if let address = viewModel.accountScoreViewModel?.address { + self?.delegate?.didTapAccountScore(address: address) + } + } } private func configure() { @@ -142,6 +153,13 @@ final class WalletsManagmentTableCell: UITableViewCell { make.trailing.equalToSuperview() make.leading.equalTo(vStackView.snp.trailing).offset(UIConstants.defaultOffset) } + + backgroundTriangularedView.addSubview(accountScoreView) + accountScoreView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.trailing.equalTo(optionsButton.snp.leading) + make.height.equalTo(15) + } } } diff --git a/fearless/Modules/WalletsManagment/WalletsManagmentViewController.swift b/fearless/Modules/WalletsManagment/WalletsManagmentViewController.swift index 5229e24c6..16f7687ba 100644 --- a/fearless/Modules/WalletsManagment/WalletsManagmentViewController.swift +++ b/fearless/Modules/WalletsManagment/WalletsManagmentViewController.swift @@ -138,4 +138,8 @@ extension WalletsManagmentViewController: WalletsManagmentTableCellDelegate { } output.didTapOptions(for: indexPath) } + + func didTapAccountScore(address: String) { + output.didTapAccountScore(address: address) + } } diff --git a/fearless/en.lproj/Localizable.strings b/fearless/en.lproj/Localizable.strings index 0462274de..878fc1d61 100644 --- a/fearless/en.lproj/Localizable.strings +++ b/fearless/en.lproj/Localizable.strings @@ -1203,4 +1203,5 @@ belongs to the right network"; "your.validators.change.validators.title" = "Change validators"; "your.validators.stop.nominating.title" = "Stop nominating"; "your.validators.validator.total.stake" = "Total staked: %@"; -"сurrencies.stub.text" = "Currencies"; \ No newline at end of file +"сurrencies.stub.text" = "Currencies"; +"account.stats.wallet.option.title" = "Show wallet score"; diff --git a/fearless/id.lproj/Localizable.strings b/fearless/id.lproj/Localizable.strings index 889a09de0..df77cb921 100644 --- a/fearless/id.lproj/Localizable.strings +++ b/fearless/id.lproj/Localizable.strings @@ -1187,4 +1187,5 @@ akan muncul di sini"; "your.validators.change.validators.title" = "Ubah validator"; "your.validators.stop.nominating.title" = "berhenti mencalonkan diri"; "your.validators.validator.total.stake" = "Total ditaruhkan: %@"; -"сurrencies.stub.text" = "Mata uang"; \ No newline at end of file +"сurrencies.stub.text" = "Mata uang"; +"account.stats.wallet.option.title" = "Show wallet score"; diff --git a/fearless/ja.lproj/Localizable.strings b/fearless/ja.lproj/Localizable.strings index 67286f7fb..f88658935 100644 --- a/fearless/ja.lproj/Localizable.strings +++ b/fearless/ja.lproj/Localizable.strings @@ -1193,4 +1193,5 @@ "your.validators.change.validators.title" = "バリデーターの変更"; "your.validators.stop.nominating.title" = "ノミネートを中止"; "your.validators.validator.total.stake" = "総ステーク:%@"; -"сurrencies.stub.text" = "通貨"; \ No newline at end of file +"сurrencies.stub.text" = "通貨"; +"account.stats.wallet.option.title" = "Show wallet score"; diff --git a/fearless/pt-PT.lproj/Localizable.strings b/fearless/pt-PT.lproj/Localizable.strings index caebb7e70..c6036fef3 100644 --- a/fearless/pt-PT.lproj/Localizable.strings +++ b/fearless/pt-PT.lproj/Localizable.strings @@ -1280,3 +1280,4 @@ To find out more, contact %@"; "common.more" = "More"; "common.activation.required" = "Activation Required"; "error.scan.qr.disabled.asset" = "The asset you want to transfer is either unsupported or turned off. Go to Asset Management, activate the asset, and then scan the QR code again."; +"account.stats.wallet.option.title" = "Show wallet score"; diff --git a/fearless/ru.lproj/Localizable.strings b/fearless/ru.lproj/Localizable.strings index d65408e06..d254b7cbf 100644 --- a/fearless/ru.lproj/Localizable.strings +++ b/fearless/ru.lproj/Localizable.strings @@ -1201,4 +1201,5 @@ Euro cash"; "your.validators.change.validators.title" = "Изменить валидаторов"; "your.validators.stop.nominating.title" = "Stop nominating"; "your.validators.validator.total.stake" = "Всего в стейкинге: %@"; -"сurrencies.stub.text" = "Токены"; \ No newline at end of file +"сurrencies.stub.text" = "Токены"; +"account.stats.wallet.option.title" = "Show wallet score"; diff --git a/fearless/tr.lproj/Localizable.strings b/fearless/tr.lproj/Localizable.strings index 6e6bad43e..642387fe3 100644 --- a/fearless/tr.lproj/Localizable.strings +++ b/fearless/tr.lproj/Localizable.strings @@ -1199,4 +1199,5 @@ ait olduğundan emin olun."; "your.validators.change.validators.title" = "Doğrulayıcıları değiştir"; "your.validators.stop.nominating.title" = "Aday göstermeyi bırak"; "your.validators.validator.total.stake" = "Toplam yatırılan miktar:%@"; -"сurrencies.stub.text" = "Para birimleri"; \ No newline at end of file +"сurrencies.stub.text" = "Para birimleri"; +"account.stats.wallet.option.title" = "Show wallet score"; diff --git a/fearless/vi.lproj/Localizable.strings b/fearless/vi.lproj/Localizable.strings index a3bc1a2a6..cac7f52f7 100644 --- a/fearless/vi.lproj/Localizable.strings +++ b/fearless/vi.lproj/Localizable.strings @@ -1201,4 +1201,5 @@ thuộc đúng mạng"; "your.validators.change.validators.title" = "Thay đổi validator"; "your.validators.stop.nominating.title" = "Dừng đề cử"; "your.validators.validator.total.stake" = "Tổng đã stake: %@"; -"сurrencies.stub.text" = "Tiền tệ"; \ No newline at end of file +"сurrencies.stub.text" = "Tiền tệ"; +"account.stats.wallet.option.title" = "Show wallet score"; diff --git a/fearless/zh-Hans.lproj/Localizable.strings b/fearless/zh-Hans.lproj/Localizable.strings index 19e7a54aa..587ee98fa 100644 --- a/fearless/zh-Hans.lproj/Localizable.strings +++ b/fearless/zh-Hans.lproj/Localizable.strings @@ -1198,4 +1198,5 @@ "your.validators.change.validators.title" = "更改验证人"; "your.validators.stop.nominating.title" = "停止提名"; "your.validators.validator.total.stake" = "总质押:%@"; -"сurrencies.stub.text" = "货币"; \ No newline at end of file +"сurrencies.stub.text" = "货币"; +"account.stats.wallet.option.title" = "Show wallet score"; From 0ead35bf73431c3f3c2959c4fbbd6014e73a23f9 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Wed, 24 Jul 2024 15:59:35 +0700 Subject: [PATCH 07/18] packages update --- fearless.xcodeproj/project.pbxproj | 10 ---------- .../xcshareddata/swiftpm/Package.resolved | 18 +++++++++--------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/fearless.xcodeproj/project.pbxproj b/fearless.xcodeproj/project.pbxproj index ba5e3b21a..ce70a43ab 100644 --- a/fearless.xcodeproj/project.pbxproj +++ b/fearless.xcodeproj/project.pbxproj @@ -6118,7 +6118,6 @@ FAD5FF262C463C4F003201F5 /* AccountStatistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountStatistics.swift; sourceTree = ""; }; FAD5FF2A2C46464B003201F5 /* NomisAccountStatisticsFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NomisAccountStatisticsFetcher.swift; sourceTree = ""; }; FAD5FF2D2C464717003201F5 /* NomisAccountStatisticsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NomisAccountStatisticsRequest.swift; sourceTree = ""; }; - FAD5FF302C4648DF003201F5 /* shared-features-spm */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "shared-features-spm"; path = "../shared-features-spm"; sourceTree = ""; }; FAD646C1284DD2CF007CCB92 /* StakingBalanceRelaychainStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingBalanceRelaychainStrategy.swift; sourceTree = ""; }; FAD646C3284DD2DA007CCB92 /* StakingBalanceRelaychainViewModelState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingBalanceRelaychainViewModelState.swift; sourceTree = ""; }; FAD646C5284DD2E5007CCB92 /* StakingBalanceRelaychainViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingBalanceRelaychainViewModelFactory.swift; sourceTree = ""; }; @@ -9166,7 +9165,6 @@ 8490139F24A80984008F705E = { isa = PBXGroup; children = ( - FAD5FF2F2C4648DF003201F5 /* Packages */, 849013AA24A80984008F705E /* fearless */, 849013C124A80986008F705E /* fearlessTests */, 8438E1D024BFAAD2001BDB13 /* fearlessIntegrationTests */, @@ -15329,14 +15327,6 @@ path = Nomis; sourceTree = ""; }; - FAD5FF2F2C4648DF003201F5 /* Packages */ = { - isa = PBXGroup; - children = ( - FAD5FF302C4648DF003201F5 /* shared-features-spm */, - ); - name = Packages; - sourceTree = ""; - }; FAD646BF284DD2B2007CCB92 /* Flow */ = { isa = PBXGroup; children = ( diff --git a/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved b/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved index d8c29c5e8..13fe4284c 100644 --- a/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -135,6 +135,15 @@ "version" : "0.1.7" } }, + { + "identity" : "shared-features-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/soramitsu/shared-features-spm.git", + "state" : { + "branch" : "fearless-wallet", + "revision" : "c914f1e729c4238bf6e18030e4f4c3895a94e088" + } + }, { "identity" : "swift-atomics", "kind" : "remoteSourceControl", @@ -225,15 +234,6 @@ "version" : "1.3.1" } }, - { - "identity" : "swiftformat", - "kind" : "remoteSourceControl", - "location" : "https://github.com/nicklockwood/SwiftFormat", - "state" : { - "revision" : "e4965d9e8acebb8341a6ebd20b910c882157482d", - "version" : "0.54.1" - } - }, { "identity" : "swiftimagereadwrite", "kind" : "remoteSourceControl", From d420269d878c72157cc30a831cfe3fb4f2270dc3 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Thu, 25 Jul 2024 13:55:23 +0700 Subject: [PATCH 08/18] dev config --- fearless/Common/Configs/ApplicationConfigs.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fearless/Common/Configs/ApplicationConfigs.swift b/fearless/Common/Configs/ApplicationConfigs.swift index 1762211e4..2ef5cce7b 100644 --- a/fearless/Common/Configs/ApplicationConfigs.swift +++ b/fearless/Common/Configs/ApplicationConfigs.swift @@ -161,11 +161,11 @@ extension ApplicationConfig: ApplicationConfigProtocol, XcmConfigProtocol { // MARK: - GitHub var chainsSourceUrl: URL { - #if F_DEV +// #if F_DEV GitHubUrl.url(suffix: "chains/v10/chains_dev.json", branch: .developFree) - #else - GitHubUrl.url(suffix: "chains/v10/chains.json") - #endif +// #else +// GitHubUrl.url(suffix: "chains/v10/chains.json") +// #endif } var chainTypesSourceUrl: URL { From 518e73a4d1b05a2ef49703aead025084f3ad59fa Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Fri, 26 Jul 2024 11:21:46 +0700 Subject: [PATCH 09/18] fix eth balance dropping --- .../AccountInfo/EthereumRemoteBalanceFetching.swift | 1 + fearless/Common/Configs/ApplicationConfigs.swift | 10 +++++----- fearless/Modules/Send/SendDependencyContainer.swift | 2 +- .../WalletMainContainerViewController.swift | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/fearless/ApplicationLayer/Services/Balance/AccountInfo/EthereumRemoteBalanceFetching.swift b/fearless/ApplicationLayer/Services/Balance/AccountInfo/EthereumRemoteBalanceFetching.swift index 030079c77..bf58d130a 100644 --- a/fearless/ApplicationLayer/Services/Balance/AccountInfo/EthereumRemoteBalanceFetching.swift +++ b/fearless/ApplicationLayer/Services/Balance/AccountInfo/EthereumRemoteBalanceFetching.swift @@ -91,6 +91,7 @@ final actor EthereumRemoteBalanceFetching { } nonisolated private func cache(accountInfo: AccountInfo?, chainAsset: ChainAsset, accountId: AccountId) throws { + guard let accountInfo else { return } let storagePath = chainAsset.storagePath let localKey = try LocalStorageKeyFactory().createFromStoragePath( diff --git a/fearless/Common/Configs/ApplicationConfigs.swift b/fearless/Common/Configs/ApplicationConfigs.swift index 2ef5cce7b..8af334621 100644 --- a/fearless/Common/Configs/ApplicationConfigs.swift +++ b/fearless/Common/Configs/ApplicationConfigs.swift @@ -161,11 +161,11 @@ extension ApplicationConfig: ApplicationConfigProtocol, XcmConfigProtocol { // MARK: - GitHub var chainsSourceUrl: URL { -// #if F_DEV - GitHubUrl.url(suffix: "chains/v10/chains_dev.json", branch: .developFree) -// #else -// GitHubUrl.url(suffix: "chains/v10/chains.json") -// #endif +#if F_DEV + GitHubUrl.url(suffix: "chains/v10/chains_dev.json", branch: .developFree) +#else + GitHubUrl.url(suffix: "chains/v10/chains.json") +#endif } var chainTypesSourceUrl: URL { diff --git a/fearless/Modules/Send/SendDependencyContainer.swift b/fearless/Modules/Send/SendDependencyContainer.swift index 1053e5c92..b5bfe48f5 100644 --- a/fearless/Modules/Send/SendDependencyContainer.swift +++ b/fearless/Modules/Send/SendDependencyContainer.swift @@ -80,7 +80,7 @@ final class SendDepencyContainer { storageRequestPerformer: storageRequestPerformer ) - cachedDependencies[chainAsset.uniqueKey(accountId: accountResponse.accountId)] = dependencies +// cachedDependencies[chainAsset.uniqueKey(accountId: accountResponse.accountId)] = dependencies currentDependecies = dependencies return dependencies diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift index c37edaa89..96f22c265 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerViewController.swift @@ -120,7 +120,7 @@ extension WalletMainContainerViewController: WalletMainContainerViewInput { func didReceiveAccountScoreViewModel(_ viewModel: AccountScoreViewModel) { rootView.bind(accountScoreViewModel: viewModel) } - + func didReceiveNftAvailability(isNftAvailable: Bool) { rootView.segmentContainer.isHidden = !isNftAvailable } From 57d04ac3e52a1ffc1e83e85486f7ffce1d1ed91d Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Tue, 30 Jul 2024 15:16:39 +0700 Subject: [PATCH 10/18] account scoring fixes --- fearless.xcodeproj/project.pbxproj | 66 +++++----- .../xcshareddata/swiftpm/Package.resolved | 16 +-- .../ScamService/ScamInfoFetcher.swift | 18 ++- .../Common/Configs/ApplicationConfigs.swift | 10 +- .../Sources/PriceDataSource.swift | 7 +- .../Common/Extension/ChainModel+Nomis.swift | 8 ++ .../Common/Extension/Decimal+Formatting.swift | 30 +++++ .../Foundation/NumberFormatter.swift | 8 ++ .../Common/Helpers/EthereumNodeFetching.swift | 6 +- fearless/Common/Model/ScamInfo.swift | 4 +- .../ChainRegistry/ChainRegistry.swift | 32 +++-- .../View/AccountScore/AccountScoreView.swift | 120 +++++++++++++++++- .../Common/View/SearchTriangularedView.swift | 22 +++- .../AccountScore/AccountScoreViewModel.swift | 10 +- .../AccountStatisticsAssembly.swift | 2 +- .../AccountStatisticsInteractor.swift | 9 +- .../AccountStatisticsPresentable.swift | 4 +- .../AccountStatisticsPresenter.swift | 5 + .../AccountStatisticsViewController.swift | 41 ++++++ .../AccountStatisticsViewLayout.swift | 9 +- .../AccountStatisticsViewModelFactory.swift | 12 +- .../BackupWalletViewModelFactory.swift | 2 +- .../Contacts/Cell/ContactTableCell.swift | 1 + .../Modules/Contacts/ContactsAssembly.swift | 2 +- .../AddressBookViewModelFactory.swift | 20 ++- .../Modules/NFT/NftSend/NftSendAssembly.swift | 5 +- .../NFT/NftSend/NftSendInteractor.swift | 2 +- .../ViewModel/ProfileViewModelFactory.swift | 2 +- fearless/Modules/Send/SendAssembly.swift | 10 +- fearless/Modules/Send/SendInteractor.swift | 4 +- fearless/Modules/Send/SendPresenter.swift | 4 +- fearless/Modules/Send/SendProtocols.swift | 3 +- .../Modules/Send/SendViewController.swift | 4 + fearless/Modules/Send/SendViewLayout.swift | 6 + .../Send/ViewModel/SendViewModelFactory.swift | 12 +- ...WalletConnectSessionViewModelFactory.swift | 2 +- .../WalletMainContainerViewModelFactory.swift | 2 +- .../WalletMainContainerPresenter.swift | 5 +- .../WalletOption/WalletOptionPresenter.swift | 5 +- .../WalletsManagmentViewModelFactory.swift | 2 +- .../WalletsManagmentTableCell.swift | 3 +- fearless/en.lproj/Localizable.strings | 2 + fearless/id.lproj/Localizable.strings | 2 + fearless/ja.lproj/Localizable.strings | 2 + fearless/pt-PT.lproj/Localizable.strings | 2 + fearless/ru.lproj/Localizable.strings | 2 + fearless/tr.lproj/Localizable.strings | 2 + fearless/vi.lproj/Localizable.strings | 2 + fearless/zh-Hans.lproj/Localizable.strings | 2 + 49 files changed, 439 insertions(+), 112 deletions(-) create mode 100644 fearless/Common/Extension/ChainModel+Nomis.swift create mode 100644 fearless/Common/Extension/Decimal+Formatting.swift diff --git a/fearless.xcodeproj/project.pbxproj b/fearless.xcodeproj/project.pbxproj index ce70a43ab..4b93ab376 100644 --- a/fearless.xcodeproj/project.pbxproj +++ b/fearless.xcodeproj/project.pbxproj @@ -2519,9 +2519,6 @@ FA8F6386298253ED004B8CD4 /* AddConnectionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8F6385298253ED004B8CD4 /* AddConnectionError.swift */; }; FA8F63AB29825C90004B8CD4 /* AccountShareFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8F63A529825C90004B8CD4 /* AccountShareFactory.swift */; }; FA8F63B1298273FE004B8CD4 /* DecodedTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8F63B0298273FE004B8CD4 /* DecodedTypes.swift */; }; - FA8FD1812AF4B55100354482 /* Web3 in Frameworks */ = {isa = PBXBuildFile; productRef = FA8FD1802AF4B55100354482 /* Web3 */; }; - FA8FD1832AF4B55100354482 /* Web3ContractABI in Frameworks */ = {isa = PBXBuildFile; productRef = FA8FD1822AF4B55100354482 /* Web3ContractABI */; }; - FA8FD1852AF4B55100354482 /* Web3PromiseKit in Frameworks */ = {isa = PBXBuildFile; productRef = FA8FD1842AF4B55100354482 /* Web3PromiseKit */; }; FA8FD1882AF4BEDD00354482 /* Swime in Frameworks */ = {isa = PBXBuildFile; productRef = FA8FD1872AF4BEDD00354482 /* Swime */; }; FA8FD18B2AFB7E6C00354482 /* AssetNetworksTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8FD18A2AFB7E6C00354482 /* AssetNetworksTableCell.swift */; }; FA8FD18E2AFBA2EA00354482 /* AssetNetworksTableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8FD18D2AFBA2EA00354482 /* AssetNetworksTableCellModel.swift */; }; @@ -2769,6 +2766,10 @@ FAB0EDE227AA9C94003D93C2 /* NodeSelectionTableCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB0EDE127AA9C94003D93C2 /* NodeSelectionTableCellViewModel.swift */; }; FAB16A312A9C9BBF00E71F43 /* NftCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB16A302A9C9BBF00E71F43 /* NftCollectionCell.swift */; }; FAB16A342A9C9BD000E71F43 /* NftCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB16A332A9C9BD000E71F43 /* NftCellViewModel.swift */; }; + FAB482EB2C58A8AA00594D89 /* Web3 in Frameworks */ = {isa = PBXBuildFile; productRef = FAB482EA2C58A8AA00594D89 /* Web3 */; }; + FAB482ED2C58A8AA00594D89 /* Web3ContractABI in Frameworks */ = {isa = PBXBuildFile; productRef = FAB482EC2C58A8AA00594D89 /* Web3ContractABI */; }; + FAB482EF2C58A8AA00594D89 /* Web3PromiseKit in Frameworks */ = {isa = PBXBuildFile; productRef = FAB482EE2C58A8AA00594D89 /* Web3PromiseKit */; }; + FAB482F12C58AC7F00594D89 /* ChainModel+Nomis.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB482F02C58AC7F00594D89 /* ChainModel+Nomis.swift */; }; FAB707622BB317D300A1131C /* CrossChainViewLoadingCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB707612BB317D300A1131C /* CrossChainViewLoadingCollector.swift */; }; FAB707652BB3C06900A1131C /* AssetsAccountRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB707642BB3C06900A1131C /* AssetsAccountRequest.swift */; }; FAB707672BB3C1A400A1131C /* AssetAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAB707662BB3C1A400A1131C /* AssetAccountInfo.swift */; }; @@ -3042,6 +3043,7 @@ FAF600772C48F08B00E56558 /* AccountScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF600762C48F08B00E56558 /* AccountScoreView.swift */; }; FAF6007A2C48F12000E56558 /* AccountScoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */; }; FAF6007C2C48FC2500E56558 /* AccountScoreViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */; }; + FAF6D90D2C57654F00274E69 /* Decimal+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF6D90C2C57654F00274E69 /* Decimal+Formatting.swift */; }; FAF92E6627B4275F005467CE /* Bool+ToInt.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF92E6527B4275E005467CE /* Bool+ToInt.swift */; }; FAF96B582B636FC700E299C1 /* SystemNumberRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF96B572B636FC700E299C1 /* SystemNumberRequest.swift */; }; FAF9C2962AAADE3300A61D21 /* DeprecatedControllerStashAccountCheckService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF9C2952AAADE3300A61D21 /* DeprecatedControllerStashAccountCheckService.swift */; }; @@ -5901,6 +5903,7 @@ FAB0EDE127AA9C94003D93C2 /* NodeSelectionTableCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeSelectionTableCellViewModel.swift; sourceTree = ""; }; FAB16A302A9C9BBF00E71F43 /* NftCollectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftCollectionCell.swift; sourceTree = ""; }; FAB16A332A9C9BD000E71F43 /* NftCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftCellViewModel.swift; sourceTree = ""; }; + FAB482F02C58AC7F00594D89 /* ChainModel+Nomis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChainModel+Nomis.swift"; sourceTree = ""; }; FAB707612BB317D300A1131C /* CrossChainViewLoadingCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrossChainViewLoadingCollector.swift; sourceTree = ""; }; FAB707642BB3C06900A1131C /* AssetsAccountRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetsAccountRequest.swift; sourceTree = ""; }; FAB707662BB3C1A400A1131C /* AssetAccountInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetAccountInfo.swift; sourceTree = ""; }; @@ -6177,6 +6180,7 @@ FAF600762C48F08B00E56558 /* AccountScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreView.swift; sourceTree = ""; }; FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreViewModel.swift; sourceTree = ""; }; FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreViewModelFactory.swift; sourceTree = ""; }; + FAF6D90C2C57654F00274E69 /* Decimal+Formatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+Formatting.swift"; sourceTree = ""; }; FAF92E6527B4275E005467CE /* Bool+ToInt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bool+ToInt.swift"; sourceTree = ""; }; FAF96B572B636FC700E299C1 /* SystemNumberRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemNumberRequest.swift; sourceTree = ""; }; FAF9C2952AAADE3300A61D21 /* DeprecatedControllerStashAccountCheckService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeprecatedControllerStashAccountCheckService.swift; sourceTree = ""; }; @@ -6298,11 +6302,10 @@ FA8810CA2BDCAF260084CC4B /* SSFUtils in Frameworks */, FAF600752C48D79600E56558 /* Cosmos in Frameworks */, FA8FD1882AF4BEDD00354482 /* Swime in Frameworks */, + FAB482ED2C58A8AA00594D89 /* Web3ContractABI in Frameworks */, FA8810BA2BDCAF260084CC4B /* SSFPools in Frameworks */, FA8810A42BDCAF260084CC4B /* SSFChainRegistry in Frameworks */, - FA8FD1832AF4B55100354482 /* Web3ContractABI in Frameworks */, FA8810B82BDCAF260084CC4B /* SSFPolkaswap in Frameworks */, - FA8FD1812AF4B55100354482 /* Web3 in Frameworks */, FA8810BE2BDCAF260084CC4B /* SSFQRService in Frameworks */, FA8810CE2BDCAF260084CC4B /* SoraKeystore in Frameworks */, FA88109E2BDCAF260084CC4B /* SSFAccountManagmentStorage in Frameworks */, @@ -6312,10 +6315,10 @@ FA8810A82BDCAF260084CC4B /* SSFCrypto in Frameworks */, FA8810C42BDCAF260084CC4B /* SSFSingleValueCache in Frameworks */, FA8810AA2BDCAF260084CC4B /* SSFEraKit in Frameworks */, - FA8FD1852AF4B55100354482 /* Web3PromiseKit in Frameworks */, FA8810D02BDCAF260084CC4B /* keccak in Frameworks */, FA8810C62BDCAF260084CC4B /* SSFStorageQueryKit in Frameworks */, FA8810B42BDCAF260084CC4B /* SSFModels in Frameworks */, + FAB482EB2C58A8AA00594D89 /* Web3 in Frameworks */, FA7254672AC2F12D00EC47A6 /* WalletConnect in Frameworks */, FA72546D2AC2F12D00EC47A6 /* WalletConnectPairing in Frameworks */, FA7254692AC2F12D00EC47A6 /* WalletConnectAuth in Frameworks */, @@ -6325,6 +6328,7 @@ FA8810C22BDCAF260084CC4B /* SSFSigner in Frameworks */, FA8810982BDCAF260084CC4B /* IrohaCrypto in Frameworks */, FA8810CC2BDCAF260084CC4B /* SSFXCM in Frameworks */, + FAB482EF2C58A8AA00594D89 /* Web3PromiseKit in Frameworks */, C5AFED6C37C2C29E9903D136 /* Pods_fearlessAll_fearless.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -9392,6 +9396,8 @@ FAA9BC402B8F17BA00A875BF /* Collection+Average.swift */, FA24FEFD2B95C32200CD9E04 /* Decimal+DoubleValue.swift */, FAC6CDA82BA814F20013A17E /* UIControl+Disable.swift */, + FAF6D90C2C57654F00274E69 /* Decimal+Formatting.swift */, + FAB482F02C58AC7F00594D89 /* ChainModel+Nomis.swift */, ); path = Extension; sourceTree = ""; @@ -15821,9 +15827,6 @@ FA72546A2AC2F12D00EC47A6 /* WalletConnectNetworking */, FA72546C2AC2F12D00EC47A6 /* WalletConnectPairing */, FA72546E2AC2F12D00EC47A6 /* Web3Wallet */, - FA8FD1802AF4B55100354482 /* Web3 */, - FA8FD1822AF4B55100354482 /* Web3ContractABI */, - FA8FD1842AF4B55100354482 /* Web3PromiseKit */, FA8FD1872AF4BEDD00354482 /* Swime */, FA8810972BDCAF260084CC4B /* IrohaCrypto */, FA8810992BDCAF260084CC4B /* RobinHood */, @@ -15855,6 +15858,9 @@ FA8810CD2BDCAF260084CC4B /* SoraKeystore */, FA8810CF2BDCAF260084CC4B /* keccak */, FAF600742C48D79600E56558 /* Cosmos */, + FAB482EA2C58A8AA00594D89 /* Web3 */, + FAB482EC2C58A8AA00594D89 /* Web3ContractABI */, + FAB482EE2C58A8AA00594D89 /* Web3PromiseKit */, ); productName = fearless; productReference = 849013A824A80984008F705E /* fearless.app */; @@ -15923,10 +15929,10 @@ mainGroup = 8490139F24A80984008F705E; packageReferences = ( FA7254652AC2F12D00EC47A6 /* XCRemoteSwiftPackageReference "WalletConnectSwiftV2" */, - FA8FD17F2AF4B55100354482 /* XCRemoteSwiftPackageReference "Web3.swift" */, FA8FD1862AF4BEDD00354482 /* XCRemoteSwiftPackageReference "Swime" */, FA8810962BDCAF260084CC4B /* XCRemoteSwiftPackageReference "shared-features-spm" */, FAF600732C48D79500E56558 /* XCRemoteSwiftPackageReference "Cosmos" */, + FAB482E92C58A8AA00594D89 /* XCRemoteSwiftPackageReference "Web3.swift" */, ); productRefGroup = 849013A924A80984008F705E /* Products */; projectDirPath = ""; @@ -18291,6 +18297,7 @@ 841E6AFE25EC12DE0007DDFE /* SelectedValidatorInfo.swift in Sources */, C89D156BA8B690E8E4DE19ED /* ExportSeedProtocols.swift in Sources */, 076D9D2E2939B780002762E3 /* PolkaswapOperationFactoryProtocol.swift in Sources */, + FAB482F12C58AC7F00594D89 /* ChainModel+Nomis.swift in Sources */, FA74359D29C0736F0085A47E /* StorageWrapper.swift in Sources */, 84B64E3F2704567700914E88 /* RelaychainStakingLocalStorageSubscriber.swift in Sources */, BD571417BD18C711B76E1D62 /* ExportSeedWireframe.swift in Sources */, @@ -18775,6 +18782,7 @@ FAD429322A865696001D6A16 /* CheckboxButton.swift in Sources */, 0DAEDA34F5BCECE5BD64DF26 /* WalletTransactionHistoryWireframe.swift in Sources */, FAD4291F2A86567F001D6A16 /* WalletNameAssembly.swift in Sources */, + FAF6D90D2C57654F00274E69 /* Decimal+Formatting.swift in Sources */, D1E085712E7BC0EBF2F4F020 /* WalletTransactionHistoryPresenter.swift in Sources */, FA7D46CD2AF24191005D681B /* SoraRewardOperationFactory.swift in Sources */, FAD429132A86567F001D6A16 /* BackupSelectWalletInteractor.swift in Sources */, @@ -19865,14 +19873,6 @@ kind = branch; }; }; - FA8FD17F2AF4B55100354482 /* XCRemoteSwiftPackageReference "Web3.swift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/bnsports/Web3.swift.git"; - requirement = { - kind = exactVersion; - version = 7.7.7; - }; - }; FA8FD1862AF4BEDD00354482 /* XCRemoteSwiftPackageReference "Swime" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sendyhalim/Swime"; @@ -19881,6 +19881,14 @@ minimumVersion = 3.0.0; }; }; + FAB482E92C58A8AA00594D89 /* XCRemoteSwiftPackageReference "Web3.swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/bnsports/Web3.swift.git"; + requirement = { + kind = exactVersion; + version = 7.7.7; + }; + }; FAF600732C48D79500E56558 /* XCRemoteSwiftPackageReference "Cosmos" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/evgenyneu/Cosmos.git"; @@ -20062,26 +20070,26 @@ package = FA8810962BDCAF260084CC4B /* XCRemoteSwiftPackageReference "shared-features-spm" */; productName = keccak; }; - FA8FD1802AF4B55100354482 /* Web3 */ = { + FA8FD1872AF4BEDD00354482 /* Swime */ = { + isa = XCSwiftPackageProductDependency; + package = FA8FD1862AF4BEDD00354482 /* XCRemoteSwiftPackageReference "Swime" */; + productName = Swime; + }; + FAB482EA2C58A8AA00594D89 /* Web3 */ = { isa = XCSwiftPackageProductDependency; - package = FA8FD17F2AF4B55100354482 /* XCRemoteSwiftPackageReference "Web3.swift" */; + package = FAB482E92C58A8AA00594D89 /* XCRemoteSwiftPackageReference "Web3.swift" */; productName = Web3; }; - FA8FD1822AF4B55100354482 /* Web3ContractABI */ = { + FAB482EC2C58A8AA00594D89 /* Web3ContractABI */ = { isa = XCSwiftPackageProductDependency; - package = FA8FD17F2AF4B55100354482 /* XCRemoteSwiftPackageReference "Web3.swift" */; + package = FAB482E92C58A8AA00594D89 /* XCRemoteSwiftPackageReference "Web3.swift" */; productName = Web3ContractABI; }; - FA8FD1842AF4B55100354482 /* Web3PromiseKit */ = { + FAB482EE2C58A8AA00594D89 /* Web3PromiseKit */ = { isa = XCSwiftPackageProductDependency; - package = FA8FD17F2AF4B55100354482 /* XCRemoteSwiftPackageReference "Web3.swift" */; + package = FAB482E92C58A8AA00594D89 /* XCRemoteSwiftPackageReference "Web3.swift" */; productName = Web3PromiseKit; }; - FA8FD1872AF4BEDD00354482 /* Swime */ = { - isa = XCSwiftPackageProductDependency; - package = FA8FD1862AF4BEDD00354482 /* XCRemoteSwiftPackageReference "Swime" */; - productName = Swime; - }; FAF600742C48D79600E56558 /* Cosmos */ = { isa = XCSwiftPackageProductDependency; package = FAF600732C48D79500E56558 /* XCRemoteSwiftPackageReference "Cosmos" */; diff --git a/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved b/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved index 13fe4284c..1a3d89765 100644 --- a/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -167,8 +167,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-http-types", "state" : { - "revision" : "1ddbea1ee34354a6a2532c60f98501c35ae8edfa", - "version" : "1.2.0" + "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd", + "version" : "1.3.0" } }, { @@ -176,8 +176,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "fc79798d5a150d61361a27ce0c51169b889e23de", - "version" : "2.68.0" + "revision" : "e4abde8be0e49dc7d66e6eed651254accdcd9533", + "version" : "2.69.0" } }, { @@ -194,8 +194,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "a0224f3d20438635dd59c9fcc593520d80d131d0", - "version" : "1.33.0" + "revision" : "b5f7062b60e4add1e8c343ba4eb8da2e324b3a94", + "version" : "1.34.0" } }, { @@ -230,8 +230,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-system.git", "state" : { - "revision" : "6a9e38e7bd22a3b8ba80bddf395623cf68f57807", - "version" : "1.3.1" + "revision" : "d2ba781702a1d8285419c15ee62fd734a9437ff5", + "version" : "1.3.2" } }, { diff --git a/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift b/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift index 0a5771390..22d1284be 100644 --- a/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift +++ b/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift @@ -1,7 +1,8 @@ import Foundation +import SSFModels protocol ScamInfoFetching { - func fetch(address: String) async throws -> ScamInfo? + func fetch(address: String, chain: ChainModel) async throws -> ScamInfo? } final class ScamInfoFetcher: ScamInfoFetching { @@ -17,13 +18,17 @@ final class ScamInfoFetcher: ScamInfoFetching { self.accountScoreFetching = accountScoreFetching } - func fetch(address: String) async throws -> ScamInfo? { + func fetch(address: String, chain: ChainModel) async throws -> ScamInfo? { let scamFeatureCheckingResult = try? await fetchScamInfo(address: address) guard scamFeatureCheckingResult == nil else { return scamFeatureCheckingResult } + guard chain.isNomisSupported else { + return nil + } + return try? await fetchAccountScore(address: address) } @@ -49,11 +54,16 @@ final class ScamInfoFetcher: ScamInfoFetching { private func fetchAccountScore(address: String) async throws -> ScamInfo? { let score: AccountStatisticsResponse? = try await accountScoreFetching.fetchStatistics(address: address) let scamInfo: ScamInfo? = score.flatMap { - guard ($0.data?.score).or(.zero) < 25 else { + guard ($0.data?.score).or(.zero) < 0.25 else { return nil } - return ScamInfo(name: "Low score", address: address, type: .lowScore, subtype: "Low network activity") + return ScamInfo( + name: "Nomis multi-chain score", + address: address, + type: .lowScore, + subtype: "Proceed with caution" + ) } return scamInfo diff --git a/fearless/Common/Configs/ApplicationConfigs.swift b/fearless/Common/Configs/ApplicationConfigs.swift index 8af334621..1762211e4 100644 --- a/fearless/Common/Configs/ApplicationConfigs.swift +++ b/fearless/Common/Configs/ApplicationConfigs.swift @@ -161,11 +161,11 @@ extension ApplicationConfig: ApplicationConfigProtocol, XcmConfigProtocol { // MARK: - GitHub var chainsSourceUrl: URL { -#if F_DEV - GitHubUrl.url(suffix: "chains/v10/chains_dev.json", branch: .developFree) -#else - GitHubUrl.url(suffix: "chains/v10/chains.json") -#endif + #if F_DEV + GitHubUrl.url(suffix: "chains/v10/chains_dev.json", branch: .developFree) + #else + GitHubUrl.url(suffix: "chains/v10/chains.json") + #endif } var chainTypesSourceUrl: URL { diff --git a/fearless/Common/DataProvider/Sources/PriceDataSource.swift b/fearless/Common/DataProvider/Sources/PriceDataSource.swift index e1e57260b..438f3a0e0 100644 --- a/fearless/Common/DataProvider/Sources/PriceDataSource.swift +++ b/fearless/Common/DataProvider/Sources/PriceDataSource.swift @@ -117,11 +117,6 @@ final class PriceDataSource: SingleValueProviderSourceProtocol { let caPriceIds = Set(chainAssets.compactMap { $0.asset.priceId }) let sqPriceIds = Set(soraSubqueryPrices.compactMap { $0.priceId }) - let replacedFiatDayChange: [PriceData] = soraSubqueryPrices.compactMap { soraSubqueryPrice in - let coingeckoPrice = coingeckoPrices.first(where: { $0.priceId == soraSubqueryPrice.priceId }) - return soraSubqueryPrice.replaceFiatDayChange(fiatDayChange: coingeckoPrice?.fiatDayChange) - } - let filtered = coingeckoPrices.filter { coingeckoPrice in let chainAsset = chainAssets.first { $0.asset.coingeckoPriceId == coingeckoPrice.priceId } guard let priceId = chainAsset?.asset.priceId else { @@ -130,7 +125,7 @@ final class PriceDataSource: SingleValueProviderSourceProtocol { return !caPriceIds.intersection(sqPriceIds).contains(priceId) } - return filtered + replacedFiatDayChange + return filtered + soraSubqueryPrices } private func makePrices(from coingeckoPrices: [PriceData], for type: PriceProviderType) -> [PriceData] { diff --git a/fearless/Common/Extension/ChainModel+Nomis.swift b/fearless/Common/Extension/ChainModel+Nomis.swift new file mode 100644 index 000000000..a81afaa5d --- /dev/null +++ b/fearless/Common/Extension/ChainModel+Nomis.swift @@ -0,0 +1,8 @@ +import Foundation +import SSFModels + +extension ChainModel { + var isNomisSupported: Bool { + chainId == "137" || chainId == "1" || chainId == "56" + } +} diff --git a/fearless/Common/Extension/Decimal+Formatting.swift b/fearless/Common/Extension/Decimal+Formatting.swift new file mode 100644 index 000000000..c7ff0dc98 --- /dev/null +++ b/fearless/Common/Extension/Decimal+Formatting.swift @@ -0,0 +1,30 @@ +import Foundation + +extension Decimal { + func string(maximumFractionDigits: Int = 2) -> String { + let decimalString = "\(self)" + + var resultString: String = "" + var pointPassed: Bool = false + var nonZeroDecimalCounter: Int = 0 + for char in decimalString { + guard nonZeroDecimalCounter < maximumFractionDigits else { + break + } + + resultString.append(char) + if char == "." { pointPassed = true } + guard pointPassed else { + continue + } + + if nonZeroDecimalCounter > 0 { + nonZeroDecimalCounter += 1 + } else if char != "0", char != "." { + nonZeroDecimalCounter += 1 + } + } + + return resultString + } +} diff --git a/fearless/Common/Extension/Foundation/NumberFormatter.swift b/fearless/Common/Extension/Foundation/NumberFormatter.swift index 1e4cd4cd3..69ae67cb5 100644 --- a/fearless/Common/Extension/Foundation/NumberFormatter.swift +++ b/fearless/Common/Extension/Foundation/NumberFormatter.swift @@ -123,4 +123,12 @@ extension NumberFormatter { formatter.usesGroupingSeparator = usesIntGrouping return formatter } + + static var nomisHours: NumberFormatter { + let formatter = NumberFormatter.amount + formatter.roundingMode = .floor + formatter.minimumFractionDigits = 0 + formatter.maximumFractionDigits = 8 + return formatter + } } diff --git a/fearless/Common/Helpers/EthereumNodeFetching.swift b/fearless/Common/Helpers/EthereumNodeFetching.swift index c6c6499cc..88f97da12 100644 --- a/fearless/Common/Helpers/EthereumNodeFetching.swift +++ b/fearless/Common/Helpers/EthereumNodeFetching.swift @@ -99,12 +99,16 @@ enum EthereumChain: String { final class EthereumNodeFetching { func getNode(for chain: ChainModel) throws -> Web3.Eth { + if let https = try? getHttps(for: chain) { + return https + } + let randomWssNode = chain.nodes.filter { $0.url.absoluteString.contains("wss") }.randomElement() let hasSelectedWssNode = chain.selectedNode?.url.absoluteString.contains("wss") == true let node = hasSelectedWssNode ? chain.selectedNode : randomWssNode guard var wssURL = node?.url else { - return try getHttps(for: chain) + throw ConvenienceError(error: "cannot obtain eth wss url for chain: \(chain.name)") } if let ethereumChain = EthereumChain(rawValue: chain.chainId) { diff --git a/fearless/Common/Model/ScamInfo.swift b/fearless/Common/Model/ScamInfo.swift index f72bda82d..de6f501eb 100644 --- a/fearless/Common/Model/ScamInfo.swift +++ b/fearless/Common/Model/ScamInfo.swift @@ -24,7 +24,7 @@ struct ScamInfo: Identifiable, Codable, Equatable, Hashable { case donation case exchange case sanctions - case lowScore + case lowScore = "Low network activity" init?(from string: String) { self.init(rawValue: string.lowercased()) @@ -56,7 +56,7 @@ struct ScamInfo: Identifiable, Codable, Equatable, Hashable { return R.string.localizable .scamDescriptionSanctionsStub(assetName, preferredLanguages: locale.rLanguages) case .lowScore: - return "This account has a low activity. Ensure that recipient isn't a scammer" + return R.string.localizable.scamDescriptionLowscoreText(preferredLanguages: locale.rLanguages) } } } diff --git a/fearless/Common/Services/ChainRegistry/ChainRegistry.swift b/fearless/Common/Services/ChainRegistry/ChainRegistry.swift index e15388df5..531d6ead7 100644 --- a/fearless/Common/Services/ChainRegistry/ChainRegistry.swift +++ b/fearless/Common/Services/ChainRegistry/ChainRegistry.swift @@ -227,16 +227,22 @@ final class ChainRegistry { return } chains.append(newChain) - _ = try ethereumConnectionPool.setupConnection(for: newChain) + + DispatchQueue.global(qos: .background).async { + _ = try? ethereumConnectionPool.setupConnection(for: newChain) + } } private func handleUpdatedEthereumChain(updatedChain: ChainModel) throws { guard let ethereumConnectionPool = self.ethereumConnectionPool else { return } - _ = try ethereumConnectionPool.setupConnection(for: updatedChain) - chains = chains.filter { $0.chainId != updatedChain.chainId } - chains.append(updatedChain) + DispatchQueue.global(qos: .background).async { [weak self] in + guard let self else { return } + _ = try? ethereumConnectionPool.setupConnection(for: updatedChain) + self.chains = self.chains.filter { $0.chainId != updatedChain.chainId } + self.chains.append(updatedChain) + } } private func handleDeletedEthereumChain(chainId: ChainModel.Id) { @@ -311,16 +317,16 @@ extension ChainRegistry: ChainRegistryProtocol { } func getEthereumConnection(for chainId: ChainModel.Id) -> Web3.Eth? { - readLock.concurrentlyRead { - guard - let ethereumConnectionPool = self.ethereumConnectionPool, - let chain = chains.first(where: { $0.chainId == chainId }) - else { - return nil - } - - return try? ethereumConnectionPool.setupConnection(for: chain) +// readLock.concurrentlyRead { + guard + let ethereumConnectionPool = self.ethereumConnectionPool, + let chain = chains.first(where: { $0.chainId == chainId }) + else { + return nil } + + return try? ethereumConnectionPool.setupConnection(for: chain) +// } } func getChain(for chainId: ChainModel.Id) -> ChainModel? { diff --git a/fearless/Common/View/AccountScore/AccountScoreView.swift b/fearless/Common/View/AccountScore/AccountScoreView.swift index 8e2b7cea1..c5a9556f5 100644 --- a/fearless/Common/View/AccountScore/AccountScoreView.swift +++ b/fearless/Common/View/AccountScore/AccountScoreView.swift @@ -1,9 +1,12 @@ import UIKit +import SoraUI import Cosmos class AccountScoreView: UIView { private var viewModel: AccountScoreViewModel? + private var skeletonView: SkrullableView? + let starView: CosmosView = { let view = CosmosView() view.settings.totalStars = 1 @@ -12,12 +15,12 @@ class AccountScoreView: UIView { view.settings.textFont = .h6Title view.settings.passTouchesToSuperview = false view.settings.fillMode = .precise + view.settings.updateOnTouch = false return view }() override init(frame: CGRect) { super.init(frame: frame) - isHidden = true addSubviews() setupConstraints() } @@ -28,12 +31,25 @@ class AccountScoreView: UIView { } func bind(viewModel: AccountScoreViewModel?) { + if viewModel?.scoringEnabled == false { + isHidden = true + return + } + + if viewModel?.address.starts(with: "0x") != true { + isHidden = false + bindEmptyViewModel() + return + } + + isHidden = false + startLoadingIfNeeded() self.viewModel = viewModel viewModel?.setup(with: self) } func bind(score: Int, rate: AccountScoreRate) { - isHidden = false + stopLoadingIfNeeded() starView.text = "\(score)" if let color = rate.color { @@ -53,7 +69,21 @@ class AccountScoreView: UIView { } } + func bindEmptyViewModel() { + starView.text = "N/A" + starView.rating = 0 + starView.settings.textFont = .p2Paragraph + + if let color = R.color.colorLightGray() { + starView.settings.emptyBorderColor = color + starView.settings.filledColor = color + starView.settings.filledBorderColor = color + starView.settings.textColor = color + } + } + private func addSubviews() { + isHidden = true addSubview(starView) } @@ -63,4 +93,90 @@ class AccountScoreView: UIView { make.height.equalTo(15) } } + + override func layoutSubviews() { + super.layoutSubviews() + didUpdateSkeletonLayout() + } +} + +extension AccountScoreView: SkeletonLoadable { + func didDisappearSkeleton() { + skeletonView?.stopSkrulling() + } + + func didAppearSkeleton() { + skeletonView?.stopSkrulling() + skeletonView?.startSkrulling() + } + + func didUpdateSkeletonLayout() { + guard let skeletonView = skeletonView else { + return + } + + if skeletonView.frame.size != frame.size { + skeletonView.removeFromSuperview() + self.skeletonView = nil + setupSkeleton() + } + } + + func startLoadingIfNeeded() { + guard skeletonView == nil else { + return + } + + starView.alpha = 0.0 + + setupSkeleton() + } + + func stopLoadingIfNeeded() { + guard skeletonView != nil else { + return + } + + skeletonView?.stopSkrulling() + skeletonView?.removeFromSuperview() + skeletonView = nil + + starView.alpha = 1.0 + } + + private func setupSkeleton() { + let spaceSize = CGSize(width: 32, height: 15) + + guard spaceSize != .zero else { + self.skeletonView = Skrull(size: .zero, decorations: [], skeletons: []).build() + return + } + + let skeletonView = Skrull( + size: spaceSize, + decorations: [], + skeletons: createSkeletons(for: spaceSize) + ) + .fillSkeletonStart(R.color.colorSkeletonStart()!) + .fillSkeletonEnd(color: R.color.colorSkeletonEnd()!) + .build() + + self.skeletonView = skeletonView + + skeletonView.frame = CGRect(origin: CGPoint(x: 0, y: spaceSize.height / 2), size: spaceSize) + skeletonView.autoresizingMask = [] + insertSubview(skeletonView, aboveSubview: self) + + skeletonView.startSkrulling() + } + + private func createSkeletons(for spaceSize: CGSize) -> [Skeletonable] { + [ + SingleSkeleton.createRow( + spaceSize: spaceSize, + position: CGPoint(x: 0, y: 0.5), + size: CGSize(width: 32, height: 15) + ) + ] + } } diff --git a/fearless/Common/View/SearchTriangularedView.swift b/fearless/Common/View/SearchTriangularedView.swift index d6e6cb4da..fa71c4a95 100644 --- a/fearless/Common/View/SearchTriangularedView.swift +++ b/fearless/Common/View/SearchTriangularedView.swift @@ -87,6 +87,8 @@ final class SearchTriangularedView: UIView { return view }() + let accountScoreView = AccountScoreView() + init(withPasteButton: Bool = false) { self.withPasteButton = withPasteButton super.init(frame: .zero) @@ -102,6 +104,11 @@ final class SearchTriangularedView: UIView { fatalError("init(coder:) has not been implemented") } + func bind(accountScoreViewModel: AccountScoreViewModel?) { + textField.text = accountScoreViewModel?.address + accountScoreView.bind(viewModel: accountScoreViewModel) + } + func updateState(icon: DrawableIcon?, clearButtonIsHidden: Bool? = nil) { if let text = textField.text, text.isNotEmpty { cleanButton.isHidden = false @@ -149,13 +156,24 @@ final class SearchTriangularedView: UIView { .default .createVerticalStackView(spacing: UIConstants.minimalOffset) addSubview(vStackView) + + vStackView.addArrangedSubview(titleLabel) + + let hStackView = UIFactory.default.createHorizontalStackView(spacing: 4) + vStackView.addArrangedSubview(hStackView) + + hStackView.addArrangedSubview(textField) + hStackView.addArrangedSubview(accountScoreView) + + accountScoreView.snp.makeConstraints { make in + make.width.equalTo(32) + } + vStackView.snp.makeConstraints { make in make.top.equalToSuperview().inset(LayoutConstants.verticalOffset) make.leading.equalTo(addressImage.snp.trailing).offset(UIConstants.defaultOffset) make.bottom.equalToSuperview().inset(LayoutConstants.verticalOffset) } - vStackView.addArrangedSubview(titleLabel) - vStackView.addArrangedSubview(textField) addSubview(cleanButton) cleanButton.snp.makeConstraints { make in diff --git a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift index 8495f9fbe..800b420b7 100644 --- a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift +++ b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift @@ -1,4 +1,5 @@ import UIKit +import SSFModels enum AccountScoreRate { case low @@ -18,9 +19,9 @@ enum AccountScoreRate { var color: UIColor? { switch self { case .low: - return R.color.colorRed() - case .medium: return R.color.colorOrange() + case .medium: + return R.color.colorYellow() case .high: return R.color.colorGreen() } @@ -30,12 +31,14 @@ enum AccountScoreRate { class AccountScoreViewModel { private let fetcher: AccountStatisticsFetching let address: String + let scoringEnabled: Bool weak var view: AccountScoreView? - init(fetcher: AccountStatisticsFetching, address: String) { + init(fetcher: AccountStatisticsFetching, address: String, chain: ChainModel?) { self.fetcher = fetcher self.address = address + scoringEnabled = chain?.isNomisSupported == true || chain == nil } func setup(with view: AccountScoreView?) { @@ -55,6 +58,7 @@ class AccountScoreViewModel { private func handle(response: AccountStatisticsResponse?) { guard let score = response?.data?.score else { + view?.bindEmptyViewModel() return } diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift b/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift index 468144b6f..41eae811a 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift @@ -3,7 +3,7 @@ import SoraFoundation import SSFNetwork final class AccountStatisticsAssembly { - static func configureModule(address: String) -> AccountStatisticsModuleCreationResult? { + static func configureModule(address: String?) -> AccountStatisticsModuleCreationResult? { let localizationManager = LocalizationManager.shared let accountScoreFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift b/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift index 4ca3b7c13..84c36d209 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsInteractor.swift @@ -3,6 +3,7 @@ import UIKit protocol AccountStatisticsInteractorOutput: AnyObject { func didReceiveAccountStatistics(_ response: AccountStatisticsResponse?) func didReceiveAccountStatisticsError(_ error: Error) + func didReceiveNoDataAvailableState() } final class AccountStatisticsInteractor { @@ -10,9 +11,9 @@ final class AccountStatisticsInteractor { private weak var output: AccountStatisticsInteractorOutput? private let accountScoreFetcher: AccountStatisticsFetching - private let address: String + private let address: String? - init(accountScoreFetcher: AccountStatisticsFetching, address: String) { + init(accountScoreFetcher: AccountStatisticsFetching, address: String?) { self.accountScoreFetcher = accountScoreFetcher self.address = address } @@ -26,6 +27,10 @@ extension AccountStatisticsInteractor: AccountStatisticsInteractorInput { } func fetchAccountStatistics() { + guard let address else { + output?.didReceiveNoDataAvailableState() + return + } Task { do { let stream = try await accountScoreFetcher.subscribeForStatistics(address: address, cacheOptions: .onAll) diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsPresentable.swift b/fearless/Modules/AccountStatistics/AccountStatisticsPresentable.swift index 12bb8521c..08e6e6a0a 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsPresentable.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsPresentable.swift @@ -2,14 +2,14 @@ import Foundation protocol AccountScorePresentable { func presentAccountScore( - address: String, + address: String?, from view: ControllerBackedProtocol? ) } extension AccountScorePresentable { func presentAccountScore( - address: String, + address: String?, from view: ControllerBackedProtocol? ) { guard let controller = view?.controller else { diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift b/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift index 2f9925eb2..49aa2a0e7 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift @@ -3,6 +3,7 @@ import SoraFoundation protocol AccountStatisticsViewInput: ControllerBackedProtocol { func didReceive(viewModel: AccountStatisticsViewModel?) + func didReceiveError() } protocol AccountStatisticsInteractorInput: AnyObject { @@ -81,6 +82,10 @@ extension AccountStatisticsPresenter: AccountStatisticsInteractorOutput { } func didReceiveAccountStatisticsError(_: Error) {} + + func didReceiveNoDataAvailableState() { + view?.didReceiveError() + } } // MARK: - Localizable diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift b/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift index f73c7a677..e82754105 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsViewController.swift @@ -1,5 +1,6 @@ import UIKit import SoraFoundation +import SoraUI protocol AccountStatisticsViewOutput: AnyObject { func didLoad(view: AccountStatisticsViewInput) @@ -13,6 +14,7 @@ final class AccountStatisticsViewController: UIViewController, ViewHolder { // MARK: Private properties private let output: AccountStatisticsViewOutput + private var shouldDisplayEmptyView: Bool = false // MARK: - Constructor @@ -60,6 +62,12 @@ final class AccountStatisticsViewController: UIViewController, ViewHolder { // MARK: - AccountStatisticsViewInput extension AccountStatisticsViewController: AccountStatisticsViewInput { + func didReceiveError() { + rootView.setupEmptyState() + shouldDisplayEmptyView = true + reloadEmptyState(animated: true) + } + func didReceive(viewModel: AccountStatisticsViewModel?) { rootView.bind(viewModel: viewModel) } @@ -72,3 +80,36 @@ extension AccountStatisticsViewController: Localizable { rootView.locale = selectedLocale } } + +// MARK: - EmptyStateViewOwnerProtocol + +extension AccountStatisticsViewController: EmptyStateViewOwnerProtocol { + var emptyStateDelegate: EmptyStateDelegate { self } + var emptyStateDataSource: EmptyStateDataSource { self } +} + +// MARK: - EmptyStateDataSource + +extension AccountStatisticsViewController: EmptyStateDataSource { + var viewForEmptyState: UIView? { + let emptyView = EmptyView() + emptyView.image = R.image.iconWarningGray() + emptyView.title = R.string.localizable + .emptyViewTitle(preferredLanguages: selectedLocale.rLanguages) + emptyView.text = R.string.localizable.accountStatsErrorMessage(preferredLanguages: selectedLocale.rLanguages) + emptyView.iconMode = .smallFilled + return emptyView + } + + var contentViewForEmptyState: UIView { + rootView.contentBackgroundView + } +} + +// MARK: - EmptyStateDelegate + +extension AccountStatisticsViewController: EmptyStateDelegate { + var shouldDisplayEmptyState: Bool { + shouldDisplayEmptyView + } +} diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift b/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift index 2359a75d8..08b65d577 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsViewLayout.swift @@ -91,6 +91,13 @@ final class AccountStatisticsViewLayout: UIView { fatalError("init(coder:) has not been implemented") } + func setupEmptyState() { + stackView.isHidden = true + addressView.isHidden = true + ratingView.rating = 0 + ratingView.settings.emptyBorderColor = R.color.colorLightGray()! + } + func bind(viewModel: AccountStatisticsViewModel?) { if let rating = viewModel?.rating { ratingView.rating = rating @@ -189,7 +196,7 @@ final class AccountStatisticsViewLayout: UIView { descriptionLabel.snp.makeConstraints { make in make.top.equalTo(ratingView.snp.bottom).offset(8) - make.leading.trailing.equalToSuperview().inset(16) + make.leading.trailing.equalToSuperview().inset(24) } scoreLabel.snp.makeConstraints { make in diff --git a/fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModelFactory.swift b/fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModelFactory.swift index 0706eac28..544122082 100644 --- a/fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModelFactory.swift +++ b/fearless/Modules/AccountStatistics/ViewModel/AccountStatisticsViewModelFactory.swift @@ -6,7 +6,7 @@ protocol AccountStatisticsViewModelFactory { } final class AccountStatisticsViewModelFactoryImpl: AccountStatisticsViewModelFactory { - private lazy var timeFormatter = TotalTimeFormatter() + private lazy var timeFormatter = HourMinuteFormatter() func buildViewModel(accountScore: AccountStatistics?, locale: Locale) -> AccountStatisticsViewModel? { guard let accountScore, let score = accountScore.score else { @@ -29,9 +29,13 @@ final class AccountStatisticsViewModelFactoryImpl: AccountStatisticsViewModelFac let walletAgeText = accountScore.stats?.walletAge.flatMap { "\($0) month" } let totalTransactionsText = accountScore.stats?.totalTransactions.flatMap { "\($0)" } let rejectedTransactionsText = accountScore.stats?.totalRejectedTransactions.flatMap { "\($0)" } - let avgTransactionTimeText = avgTxTime.flatMap { try? timeFormatter.string(from: $0) } - let maxTransactionTimeText = maxTxTime.flatMap { try? timeFormatter.string(from: $0) } - let minTransactionTimeText = minTxTime.flatMap { try? timeFormatter.string(from: $0) } + + let avgTransactionTimeText = avgTxTime + .flatMap { "\(Decimal($0).string(maximumFractionDigits: 2)) hours" } + let maxTransactionTimeText = maxTxTime + .flatMap { "\(Decimal($0).string(maximumFractionDigits: 2)) hours" } + let minTransactionTimeText = minTxTime + .flatMap { "\(Decimal($0).string(maximumFractionDigits: 2)) hours" } return AccountStatisticsViewModel( rating: doubleScore, diff --git a/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift b/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift index 410880d5d..40e849caf 100644 --- a/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift +++ b/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift @@ -153,7 +153,7 @@ final class BackupWalletViewModelFactory: BackupWalletViewModelFactoryProtocol { locale: Locale ) -> WalletsManagmentCellViewModel { let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0) } + let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } var fiatBalance: String = "" var dayChange: NSAttributedString? diff --git a/fearless/Modules/Contacts/Cell/ContactTableCell.swift b/fearless/Modules/Contacts/Cell/ContactTableCell.swift index 974963f4a..c9fe9f426 100644 --- a/fearless/Modules/Contacts/Cell/ContactTableCell.swift +++ b/fearless/Modules/Contacts/Cell/ContactTableCell.swift @@ -45,6 +45,7 @@ class ContactTableCell: UITableViewCell { private let hStackView: UIStackView = { let stackView = UIFactory.default.createHorizontalStackView() + stackView.spacing = 4 stackView.distribution = .fill return stackView }() diff --git a/fearless/Modules/Contacts/ContactsAssembly.swift b/fearless/Modules/Contacts/ContactsAssembly.swift index 1bd65fddd..a3db9f01c 100644 --- a/fearless/Modules/Contacts/ContactsAssembly.swift +++ b/fearless/Modules/Contacts/ContactsAssembly.swift @@ -63,7 +63,7 @@ enum ContactsAssembly { interactor: interactor, router: router, localizationManager: localizationManager, - viewModelFactory: AddressBookViewModelFactory(accountScoreFetcher: accountScoreFetcher), + viewModelFactory: AddressBookViewModelFactory(accountScoreFetcher: accountScoreFetcher, chain: source.chain), moduleOutput: moduleOutput, source: source, wallet: wallet diff --git a/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift b/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift index 65f268afe..c53bf64f2 100644 --- a/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift +++ b/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift @@ -1,4 +1,5 @@ import Foundation +import SSFModels protocol AddressBookViewModelFactoryProtocol { func buildCellViewModels( @@ -16,9 +17,14 @@ struct ContactsTableSectionModel { final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { private let accountScoreFetcher: AccountStatisticsFetching + private let chain: ChainModel - init(accountScoreFetcher: AccountStatisticsFetching) { + init( + accountScoreFetcher: AccountStatisticsFetching, + chain: ChainModel + ) { self.accountScoreFetcher = accountScoreFetcher + self.chain = chain } func buildCellViewModels( @@ -29,7 +35,11 @@ final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { ) -> [ContactsTableSectionModel] { let recentContactsViewModels = recentContacts.map { contactType in - let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: contactType.address) + let accountScoreViewModel = AccountScoreViewModel( + fetcher: accountScoreFetcher, + address: contactType.address, + chain: chain + ) return ContactTableCellModel( contactType: contactType, @@ -53,7 +63,11 @@ final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { contact.name.first?.lowercased() == firstLetter.lowercased() } let cellModels = contacts.map { contact in - let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: contact.address) + let accountScoreViewModel = AccountScoreViewModel( + fetcher: accountScoreFetcher, + address: contact.address, + chain: chain + ) return ContactTableCellModel(contactType: .saved(contact), delegate: cellsDelegate, accountScoreViewModel: accountScoreViewModel) } diff --git a/fearless/Modules/NFT/NftSend/NftSendAssembly.swift b/fearless/Modules/NFT/NftSend/NftSendAssembly.swift index feafe6412..f73695b99 100644 --- a/fearless/Modules/NFT/NftSend/NftSendAssembly.swift +++ b/fearless/Modules/NFT/NftSend/NftSendAssembly.swift @@ -69,7 +69,10 @@ enum NftSendAssembly { wallet: wallet, logger: Logger.shared, viewModelFactory: - SendViewModelFactory(iconGenerator: UniversalIconGenerator()), + SendViewModelFactory( + iconGenerator: UniversalIconGenerator(), + accountScoreFetcher: accountStatisticsFetcher + ), dataValidatingFactory: dataValidatingFactory ) diff --git a/fearless/Modules/NFT/NftSend/NftSendInteractor.swift b/fearless/Modules/NFT/NftSend/NftSendInteractor.swift index cbe846e45..66e0f67e9 100644 --- a/fearless/Modules/NFT/NftSend/NftSendInteractor.swift +++ b/fearless/Modules/NFT/NftSend/NftSendInteractor.swift @@ -92,7 +92,7 @@ extension NftSendInteractor: NftSendInteractorInput { func fetchScamInfo(for address: String) { Task { - let scamInfo = try await scamInfoFetching.fetch(address: address) + let scamInfo = try await scamInfoFetching.fetch(address: address, chain: chain) output?.didReceive(scamInfo: scamInfo) } } diff --git a/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift b/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift index 67f7bbe63..cfc925059 100644 --- a/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift +++ b/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift @@ -108,7 +108,7 @@ final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { } let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0) } + let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } return WalletsManagmentCellViewModel( isSelected: false, diff --git a/fearless/Modules/Send/SendAssembly.swift b/fearless/Modules/Send/SendAssembly.swift index f34d5cd71..35d820a6f 100644 --- a/fearless/Modules/Send/SendAssembly.swift +++ b/fearless/Modules/Send/SendAssembly.swift @@ -56,7 +56,10 @@ final class SendAssembly { let runtimeMetadataRepository: AsyncCoreDataRepositoryDefault = SubstrateDataStorageFacade.shared.createAsyncRepository() let accountStatisticsFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) - let scamInfoFetcher = ScamInfoFetcher(scamServiceOperationFactory: scamServiceOperationFactory, accountScoreFetching: accountStatisticsFetcher) + let scamInfoFetcher = ScamInfoFetcher( + scamServiceOperationFactory: scamServiceOperationFactory, + accountScoreFetching: accountStatisticsFetcher + ) let interactor = SendInteractor( accountInfoSubscriptionAdapter: AccountInfoSubscriptionAdapter( walletLocalSubscriptionFactory: WalletLocalSubscriptionFactory.shared, @@ -72,7 +75,10 @@ final class SendAssembly { ) let router = SendRouter() - let viewModelFactory = SendViewModelFactory(iconGenerator: UniversalIconGenerator()) + let viewModelFactory = SendViewModelFactory( + iconGenerator: UniversalIconGenerator(), + accountScoreFetcher: accountStatisticsFetcher + ) let dataValidatingFactory = SendDataValidatingFactory(presentable: router) let presenter = SendPresenter( interactor: interactor, diff --git a/fearless/Modules/Send/SendInteractor.swift b/fearless/Modules/Send/SendInteractor.swift index 76c6ca1b1..e5cb24b0f 100644 --- a/fearless/Modules/Send/SendInteractor.swift +++ b/fearless/Modules/Send/SendInteractor.swift @@ -175,9 +175,9 @@ extension SendInteractor: SendInteractorInput { dependencies.transferService.subscribeForFee(transfer: transfer, listener: self) } - func fetchScamInfo(for address: String) { + func fetchScamInfo(for address: String, chain: ChainModel) { Task { - let scamInfo = try await scamInfoFetching.fetch(address: address) + let scamInfo = try await scamInfoFetching.fetch(address: address, chain: chain) output?.didReceive(scamInfo: scamInfo) } } diff --git a/fearless/Modules/Send/SendPresenter.swift b/fearless/Modules/Send/SendPresenter.swift index 03e6b49a5..77e820bd3 100644 --- a/fearless/Modules/Send/SendPresenter.swift +++ b/fearless/Modules/Send/SendPresenter.swift @@ -279,12 +279,14 @@ final class SendPresenter { canEditing: true ) + let accountScoreViewModel = viewModelFactory.buildAccountScoreViewModel(address: newAddress, chain: chainAsset.chain) DispatchQueue.main.async { self.view?.didReceive(viewModel: viewModel) + self.view?.didReceive(accountScoreViewModel: accountScoreViewModel) } interactor.updateSubscriptions(for: chainAsset) - interactor.fetchScamInfo(for: newAddress) + interactor.fetchScamInfo(for: newAddress, chain: chainAsset.chain) } private func handle(selectedChain: ChainModel?) { diff --git a/fearless/Modules/Send/SendProtocols.swift b/fearless/Modules/Send/SendProtocols.swift index 30cb15a88..89b33f48c 100644 --- a/fearless/Modules/Send/SendProtocols.swift +++ b/fearless/Modules/Send/SendProtocols.swift @@ -15,6 +15,7 @@ protocol SendViewInput: ControllerBackedProtocol, LoadableViewProtocol { func didStopFeeCalculation() func didStopTipCalculation() func didReceive(viewModel: RecipientViewModel) + func didReceive(accountScoreViewModel: AccountScoreViewModel?) func didBlockUserInteractive(isUserInteractiveAmount: Bool) func setInputAccessoryView(visible: Bool) func setHistoryButton(isVisible: Bool) @@ -49,7 +50,7 @@ protocol SendInteractorInput: AnyObject { ) func estimateFee(for amount: BigUInt, tip: BigUInt?, for address: String?, chainAsset: ChainAsset) func validate(address: String?, for chain: ChainModel) -> AddressValidationResult - func fetchScamInfo(for address: String) + func fetchScamInfo(for address: String, chain: ChainModel) func getFeePaymentChainAsset(for chainAsset: ChainAsset?) -> ChainAsset? func getPossibleChains(for address: String) async -> [ChainModel]? func calculateEquilibriumBalance(chainAsset: ChainAsset, amount: Decimal) diff --git a/fearless/Modules/Send/SendViewController.swift b/fearless/Modules/Send/SendViewController.swift index 7146b1126..965f642be 100644 --- a/fearless/Modules/Send/SendViewController.swift +++ b/fearless/Modules/Send/SendViewController.swift @@ -200,6 +200,10 @@ extension SendViewController: SendViewInput { rootView.bind(viewModel: viewModel) } + func didReceive(accountScoreViewModel: AccountScoreViewModel?) { + rootView.bind(accountScoreViewModel: accountScoreViewModel) + } + func didStartLoading() { rootView.actionButton.set(loading: true) } diff --git a/fearless/Modules/Send/SendViewLayout.swift b/fearless/Modules/Send/SendViewLayout.swift index 8400b9207..07e403d6d 100644 --- a/fearless/Modules/Send/SendViewLayout.swift +++ b/fearless/Modules/Send/SendViewLayout.swift @@ -119,6 +119,8 @@ final class SendViewLayout: UIView { } } + let accountScoreView = AccountScoreView() + var keyboardAdoptableConstraint: Constraint? override init(frame: CGRect) { @@ -166,6 +168,10 @@ final class SendViewLayout: UIView { scamWarningView.bind(scamInfo: scamInfo, assetName: amountView.symbol ?? "") } + func bind(accountScoreViewModel: AccountScoreViewModel?) { + searchView.bind(accountScoreViewModel: accountScoreViewModel) + } + func bind(viewModel: RecipientViewModel) { searchView.textField.text = viewModel.address searchView.updateState(icon: viewModel.icon, clearButtonIsHidden: !viewModel.canEditing) diff --git a/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift b/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift index 91e0c7a81..ae605643e 100644 --- a/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift +++ b/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift @@ -8,13 +8,19 @@ protocol SendViewModelFactoryProtocol { canEditing: Bool ) -> RecipientViewModel func buildNetworkViewModel(chain: ChainModel, canEdit: Bool) -> SelectNetworkViewModel + func buildAccountScoreViewModel(address: String, chain: ChainModel) -> AccountScoreViewModel? } final class SendViewModelFactory: SendViewModelFactoryProtocol { private let iconGenerator: IconGenerating + private let accountScoreFetcher: AccountStatisticsFetching - init(iconGenerator: IconGenerating) { + init( + iconGenerator: IconGenerating, + accountScoreFetcher: AccountStatisticsFetching + ) { self.iconGenerator = iconGenerator + self.accountScoreFetcher = accountScoreFetcher } func buildRecipientViewModel( @@ -38,4 +44,8 @@ final class SendViewModelFactory: SendViewModelFactoryProtocol { canEdit: canEdit ) } + + func buildAccountScoreViewModel(address: String, chain: ChainModel) -> AccountScoreViewModel? { + AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: chain) + } } diff --git a/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift b/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift index 36e614f5a..ebf674d7c 100644 --- a/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift +++ b/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift @@ -106,7 +106,7 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo locale: Locale ) -> WalletsManagmentCellViewModel { let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0) } + let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } guard let balance = balanceInfo?[wallet.metaId] else { return WalletsManagmentCellViewModel( diff --git a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift index f44a2b7a7..6abe19930 100644 --- a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift +++ b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift @@ -53,7 +53,7 @@ final class WalletMainContainerViewModelFactory: WalletMainContainerViewModelFac } let ethAddress = selectedMetaAccount.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = ethAddress.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0) } + let accountScoreViewModel = ethAddress.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } return WalletMainContainerViewModel( walletName: selectedMetaAccount.name, diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift index 2eb2a2bae..e06837fe3 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerPresenter.swift @@ -116,10 +116,7 @@ extension WalletMainContainerPresenter: WalletMainContainerViewOutput { } func didTapAccountScore() { - guard let address = wallet.ethereumAddress?.toHex(includePrefix: true) else { - return - } - + let address = wallet.ethereumAddress?.toHex(includePrefix: true) router.presentAccountScore(address: address, from: view) } } diff --git a/fearless/Modules/WalletOption/WalletOptionPresenter.swift b/fearless/Modules/WalletOption/WalletOptionPresenter.swift index 239f7924b..f14dc4da2 100644 --- a/fearless/Modules/WalletOption/WalletOptionPresenter.swift +++ b/fearless/Modules/WalletOption/WalletOptionPresenter.swift @@ -78,10 +78,7 @@ extension WalletOptionPresenter: WalletOptionViewOutput { } func accountScoreDidTap() { - guard let address = wallet.info.ethereumAddress?.toHex(includePrefix: true) else { - return - } - + let address = wallet.info.ethereumAddress?.toHex(includePrefix: true) router.presentAccountScore(address: address, from: view) } diff --git a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift index 78efdbe3d..64faa7da3 100644 --- a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift +++ b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift @@ -41,7 +41,7 @@ final class WalletsManagmentViewModelFactory: WalletsManagmentViewModelFactoryPr } let address = managedMetaAccount.info.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0) } + let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } guard let walletBalance = balances[key] else { return WalletsManagmentCellViewModel( diff --git a/fearless/Modules/WalletsManagment/WalletsManagmentTableCell.swift b/fearless/Modules/WalletsManagment/WalletsManagmentTableCell.swift index de125f2aa..d6fdc04d7 100644 --- a/fearless/Modules/WalletsManagment/WalletsManagmentTableCell.swift +++ b/fearless/Modules/WalletsManagment/WalletsManagmentTableCell.swift @@ -157,8 +157,9 @@ final class WalletsManagmentTableCell: UITableViewCell { backgroundTriangularedView.addSubview(accountScoreView) accountScoreView.snp.makeConstraints { make in make.centerY.equalToSuperview() - make.trailing.equalTo(optionsButton.snp.leading) + make.trailing.equalTo(optionsButton.snp.leading).offset(-8) make.height.equalTo(15) + make.width.equalTo(32) } } } diff --git a/fearless/en.lproj/Localizable.strings b/fearless/en.lproj/Localizable.strings index 878fc1d61..e8311baac 100644 --- a/fearless/en.lproj/Localizable.strings +++ b/fearless/en.lproj/Localizable.strings @@ -1205,3 +1205,5 @@ belongs to the right network"; "your.validators.validator.total.stake" = "Total staked: %@"; "сurrencies.stub.text" = "Currencies"; "account.stats.wallet.option.title" = "Show wallet score"; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; diff --git a/fearless/id.lproj/Localizable.strings b/fearless/id.lproj/Localizable.strings index df77cb921..6ce9c6b71 100644 --- a/fearless/id.lproj/Localizable.strings +++ b/fearless/id.lproj/Localizable.strings @@ -1189,3 +1189,5 @@ akan muncul di sini"; "your.validators.validator.total.stake" = "Total ditaruhkan: %@"; "сurrencies.stub.text" = "Mata uang"; "account.stats.wallet.option.title" = "Show wallet score"; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; diff --git a/fearless/ja.lproj/Localizable.strings b/fearless/ja.lproj/Localizable.strings index f88658935..c28fc6a24 100644 --- a/fearless/ja.lproj/Localizable.strings +++ b/fearless/ja.lproj/Localizable.strings @@ -1195,3 +1195,5 @@ "your.validators.validator.total.stake" = "総ステーク:%@"; "сurrencies.stub.text" = "通貨"; "account.stats.wallet.option.title" = "Show wallet score"; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; diff --git a/fearless/pt-PT.lproj/Localizable.strings b/fearless/pt-PT.lproj/Localizable.strings index c6036fef3..33be92614 100644 --- a/fearless/pt-PT.lproj/Localizable.strings +++ b/fearless/pt-PT.lproj/Localizable.strings @@ -1281,3 +1281,5 @@ To find out more, contact %@"; "common.activation.required" = "Activation Required"; "error.scan.qr.disabled.asset" = "The asset you want to transfer is either unsupported or turned off. Go to Asset Management, activate the asset, and then scan the QR code again."; "account.stats.wallet.option.title" = "Show wallet score"; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; diff --git a/fearless/ru.lproj/Localizable.strings b/fearless/ru.lproj/Localizable.strings index d254b7cbf..f7ff56506 100644 --- a/fearless/ru.lproj/Localizable.strings +++ b/fearless/ru.lproj/Localizable.strings @@ -1203,3 +1203,5 @@ Euro cash"; "your.validators.validator.total.stake" = "Всего в стейкинге: %@"; "сurrencies.stub.text" = "Токены"; "account.stats.wallet.option.title" = "Show wallet score"; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; diff --git a/fearless/tr.lproj/Localizable.strings b/fearless/tr.lproj/Localizable.strings index 642387fe3..e11725bfc 100644 --- a/fearless/tr.lproj/Localizable.strings +++ b/fearless/tr.lproj/Localizable.strings @@ -1201,3 +1201,5 @@ ait olduğundan emin olun."; "your.validators.validator.total.stake" = "Toplam yatırılan miktar:%@"; "сurrencies.stub.text" = "Para birimleri"; "account.stats.wallet.option.title" = "Show wallet score"; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; diff --git a/fearless/vi.lproj/Localizable.strings b/fearless/vi.lproj/Localizable.strings index cac7f52f7..f7adc1aee 100644 --- a/fearless/vi.lproj/Localizable.strings +++ b/fearless/vi.lproj/Localizable.strings @@ -1203,3 +1203,5 @@ thuộc đúng mạng"; "your.validators.validator.total.stake" = "Tổng đã stake: %@"; "сurrencies.stub.text" = "Tiền tệ"; "account.stats.wallet.option.title" = "Show wallet score"; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; diff --git a/fearless/zh-Hans.lproj/Localizable.strings b/fearless/zh-Hans.lproj/Localizable.strings index 587ee98fa..f3b101350 100644 --- a/fearless/zh-Hans.lproj/Localizable.strings +++ b/fearless/zh-Hans.lproj/Localizable.strings @@ -1200,3 +1200,5 @@ "your.validators.validator.total.stake" = "总质押:%@"; "сurrencies.stub.text" = "货币"; "account.stats.wallet.option.title" = "Show wallet score"; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; From 89d9393c82c05c4dc2e1abbc9a7063eba33816e2 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Tue, 30 Jul 2024 15:18:03 +0700 Subject: [PATCH 11/18] dev config --- fearless/Common/Configs/ApplicationConfigs.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fearless/Common/Configs/ApplicationConfigs.swift b/fearless/Common/Configs/ApplicationConfigs.swift index 1762211e4..2ef5cce7b 100644 --- a/fearless/Common/Configs/ApplicationConfigs.swift +++ b/fearless/Common/Configs/ApplicationConfigs.swift @@ -161,11 +161,11 @@ extension ApplicationConfig: ApplicationConfigProtocol, XcmConfigProtocol { // MARK: - GitHub var chainsSourceUrl: URL { - #if F_DEV +// #if F_DEV GitHubUrl.url(suffix: "chains/v10/chains_dev.json", branch: .developFree) - #else - GitHubUrl.url(suffix: "chains/v10/chains.json") - #endif +// #else +// GitHubUrl.url(suffix: "chains/v10/chains.json") +// #endif } var chainTypesSourceUrl: URL { From 2bbdfa09bf049fcd09d549d925b50ea18ed49e67 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Wed, 31 Jul 2024 15:36:24 +0700 Subject: [PATCH 12/18] nomis fixes: toggle for visible management, touch location increased --- fearless.xcodeproj/project.pbxproj | 12 ++++-- .../Contents.json | 12 ++++++ .../iconProfleAccountScore.imageset/Star.pdf | Bin 0 -> 2560 bytes .../Common/Configs/ApplicationConfigs.swift | 8 ++-- .../Common/EventCenter/EventVisitor.swift | 2 + .../Events/AccountScoreSettingsChanged.swift | 7 ++++ fearless/Common/Extension/FWCosmosView.swift | 28 +++++++++++++ .../Common/Extension/SettingsExtension.swift | 15 +++++++ .../View/AccountScore/AccountScoreView.swift | 16 +++++--- .../AccountScore/AccountScoreViewModel.swift | 37 ++++++++++++++++-- .../AccountScoreViewModelFactory.swift | 17 -------- .../BackupWallet/BackupWalletAssembly.swift | 2 +- .../BackupWalletViewModelFactory.swift | 7 +++- .../Modules/Contacts/ContactsAssembly.swift | 7 +++- .../AddressBookViewModelFactory.swift | 14 +++++-- .../Modules/NFT/NftSend/NftSendAssembly.swift | 3 +- .../Modules/Profile/ProfilePresenter.swift | 7 +++- .../ViewModel/ProfileViewModelFactory.swift | 19 ++++++++- fearless/Modules/Send/SendAssembly.swift | 4 +- .../Send/ViewModel/SendViewModelFactory.swift | 14 ++++++- .../WalletConnectSessionAssembly.swift | 4 +- ...WalletConnectSessionViewModelFactory.swift | 8 +++- .../WalletMainContainerViewModelFactory.swift | 7 +++- .../WalletMainContainerAssembly.swift | 6 ++- .../WalletsManagmentViewModelFactory.swift | 9 ++++- fearless/en.lproj/Localizable.strings | 1 + fearless/id.lproj/Localizable.strings | 1 + fearless/ja.lproj/Localizable.strings | 1 + fearless/pt-PT.lproj/Localizable.strings | 1 + fearless/ru.lproj/Localizable.strings | 1 + fearless/tr.lproj/Localizable.strings | 1 + fearless/vi.lproj/Localizable.strings | 1 + fearless/zh-Hans.lproj/Localizable.strings | 1 + 33 files changed, 219 insertions(+), 54 deletions(-) create mode 100644 fearless/Assets.xcassets/iconProfleAccountScore.imageset/Contents.json create mode 100644 fearless/Assets.xcassets/iconProfleAccountScore.imageset/Star.pdf create mode 100644 fearless/Common/EventCenter/Events/AccountScoreSettingsChanged.swift create mode 100644 fearless/Common/Extension/FWCosmosView.swift delete mode 100644 fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift diff --git a/fearless.xcodeproj/project.pbxproj b/fearless.xcodeproj/project.pbxproj index 4b93ab376..0469f366c 100644 --- a/fearless.xcodeproj/project.pbxproj +++ b/fearless.xcodeproj/project.pbxproj @@ -3042,7 +3042,6 @@ FAF600752C48D79600E56558 /* Cosmos in Frameworks */ = {isa = PBXBuildFile; productRef = FAF600742C48D79600E56558 /* Cosmos */; }; FAF600772C48F08B00E56558 /* AccountScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF600762C48F08B00E56558 /* AccountScoreView.swift */; }; FAF6007A2C48F12000E56558 /* AccountScoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */; }; - FAF6007C2C48FC2500E56558 /* AccountScoreViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */; }; FAF6D90D2C57654F00274E69 /* Decimal+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF6D90C2C57654F00274E69 /* Decimal+Formatting.swift */; }; FAF92E6627B4275F005467CE /* Bool+ToInt.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF92E6527B4275E005467CE /* Bool+ToInt.swift */; }; FAF96B582B636FC700E299C1 /* SystemNumberRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF96B572B636FC700E299C1 /* SystemNumberRequest.swift */; }; @@ -3059,6 +3058,8 @@ FAF9C2B42AAF3FDF00A61D21 /* GetPreinstalledWalletProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF9C2AD2AAF3FDF00A61D21 /* GetPreinstalledWalletProtocols.swift */; }; FAF9C2B72AAF3FF100A61D21 /* GetPreinstalledWalletTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF9C2B62AAF3FF100A61D21 /* GetPreinstalledWalletTests.swift */; }; FAFB47D72ABD589C0008F8CA /* EthereumBalanceRepositoryCacheWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFB47D62ABD589C0008F8CA /* EthereumBalanceRepositoryCacheWrapper.swift */; }; + FAFB5EE02C5A11A30015D3DD /* AccountScoreSettingsChanged.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFB5EDF2C5A11A30015D3DD /* AccountScoreSettingsChanged.swift */; }; + FAFB5EE22C5A2CE80015D3DD /* FWCosmosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFB5EE12C5A2CE80015D3DD /* FWCosmosView.swift */; }; FAFBEE81284621800036D08C /* SelectedValidatorListParachainViewModelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFBEE80284621800036D08C /* SelectedValidatorListParachainViewModelState.swift */; }; FAFBEE83284621900036D08C /* SelectedValidatorListParachainViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFBEE82284621900036D08C /* SelectedValidatorListParachainViewModelFactory.swift */; }; FAFDB2C329112A00003971FB /* SubstrateCallPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFDB2C229112A00003971FB /* SubstrateCallPath.swift */; }; @@ -6179,7 +6180,6 @@ FAF5E9E027E4A4C1005A3448 /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = ""; }; FAF600762C48F08B00E56558 /* AccountScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreView.swift; sourceTree = ""; }; FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreViewModel.swift; sourceTree = ""; }; - FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreViewModelFactory.swift; sourceTree = ""; }; FAF6D90C2C57654F00274E69 /* Decimal+Formatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+Formatting.swift"; sourceTree = ""; }; FAF92E6527B4275E005467CE /* Bool+ToInt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bool+ToInt.swift"; sourceTree = ""; }; FAF96B572B636FC700E299C1 /* SystemNumberRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemNumberRequest.swift; sourceTree = ""; }; @@ -6202,6 +6202,8 @@ FAF9C2AD2AAF3FDF00A61D21 /* GetPreinstalledWalletProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPreinstalledWalletProtocols.swift; sourceTree = ""; }; FAF9C2B62AAF3FF100A61D21 /* GetPreinstalledWalletTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPreinstalledWalletTests.swift; sourceTree = ""; }; FAFB47D62ABD589C0008F8CA /* EthereumBalanceRepositoryCacheWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumBalanceRepositoryCacheWrapper.swift; sourceTree = ""; }; + FAFB5EDF2C5A11A30015D3DD /* AccountScoreSettingsChanged.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreSettingsChanged.swift; sourceTree = ""; }; + FAFB5EE12C5A2CE80015D3DD /* FWCosmosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FWCosmosView.swift; sourceTree = ""; }; FAFBEE80284621800036D08C /* SelectedValidatorListParachainViewModelState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedValidatorListParachainViewModelState.swift; sourceTree = ""; }; FAFBEE82284621900036D08C /* SelectedValidatorListParachainViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedValidatorListParachainViewModelFactory.swift; sourceTree = ""; }; FAFDB2C229112A00003971FB /* SubstrateCallPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubstrateCallPath.swift; sourceTree = ""; }; @@ -9398,6 +9400,7 @@ FAC6CDA82BA814F20013A17E /* UIControl+Disable.swift */, FAF6D90C2C57654F00274E69 /* Decimal+Formatting.swift */, FAB482F02C58AC7F00594D89 /* ChainModel+Nomis.swift */, + FAFB5EE12C5A2CE80015D3DD /* FWCosmosView.swift */, ); path = Extension; sourceTree = ""; @@ -10688,6 +10691,7 @@ FA37AE4C28603C37001DCA96 /* StakingUpdatedEvent.swift */, FAE39AF22A9E1A4F0011A9D6 /* ChainsSetupCompleted.swift */, C65C7F6A2AD82B8D0069D877 /* LogoutEvent.swift */, + FAFB5EDF2C5A11A30015D3DD /* AccountScoreSettingsChanged.swift */, ); path = Events; sourceTree = ""; @@ -15590,7 +15594,6 @@ isa = PBXGroup; children = ( FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */, - FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */, ); path = AccountScore; sourceTree = ""; @@ -18326,6 +18329,7 @@ FAF9C2B22AAF3FDF00A61D21 /* GetPreinstalledWalletPresenter.swift in Sources */, 90EFE3768F1375470FDBE6F6 /* PurchaseViewFactory.swift in Sources */, F4A198EE2631AA2000CD6E61 /* StakingBalanceUnbondingItemView.swift in Sources */, + FAFB5EE22C5A2CE80015D3DD /* FWCosmosView.swift in Sources */, C63468E228F2C964005CB1F1 /* ContactTableCell.swift in Sources */, 076D9D57294B38E1002762E3 /* PolkaswapPreviewParams.swift in Sources */, FA99425528053C8800D771E5 /* SelectExportAccountInteractor.swift in Sources */, @@ -18513,6 +18517,7 @@ FAADC1A529261F7000DA9903 /* PoolRolesConfirmProtocols.swift in Sources */, F022F1444E0F75CCA42F4648 /* YourValidatorListProtocols.swift in Sources */, 8493D0E326FF571D00A28008 /* PriceProviderFactory.swift in Sources */, + FAFB5EE02C5A11A30015D3DD /* AccountScoreSettingsChanged.swift in Sources */, F7EB8F835CFA7FC949EF4C22 /* YourValidatorListWireframe.swift in Sources */, AEA0C8AC267B6B5200F9666F /* SelectedValidatorListViewFactory.swift in Sources */, DBA436B3B1C90965FE8F9B79 /* YourValidatorListPresenter.swift in Sources */, @@ -18956,7 +18961,6 @@ B15D513381B7626AB90018F0 /* StakingPoolInfoInteractor.swift in Sources */, 89C8A9B990B08016A70ED336 /* StakingPoolInfoViewController.swift in Sources */, 281D17EF8C45A1FC49FD1523 /* StakingPoolInfoViewLayout.swift in Sources */, - FAF6007C2C48FC2500E56558 /* AccountScoreViewModelFactory.swift in Sources */, 39DA5795FB9DBF626B72B5C6 /* StakingPoolInfoAssembly.swift in Sources */, 4A63ECA587C601999AAEB974 /* StakingPoolCreateProtocols.swift in Sources */, 7BC6FB48B9B4EC790923FF1E /* StakingPoolCreateRouter.swift in Sources */, diff --git a/fearless/Assets.xcassets/iconProfleAccountScore.imageset/Contents.json b/fearless/Assets.xcassets/iconProfleAccountScore.imageset/Contents.json new file mode 100644 index 000000000..66ce12636 --- /dev/null +++ b/fearless/Assets.xcassets/iconProfleAccountScore.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Star.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/fearless/Assets.xcassets/iconProfleAccountScore.imageset/Star.pdf b/fearless/Assets.xcassets/iconProfleAccountScore.imageset/Star.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ed199259af094a301b24875b7f4198ac911fb820 GIT binary patch literal 2560 zcmY!laB!u zh5UTJ%$HyH)=d*@&Yu@AH@`n4?e6i0sEB7)_6L=Ygd`f3e5rGjKK(2vdY2t@YPlop zrRfp#`WKrQZw&QG)qR@?Z(>dTrF+s?{d zTkWH`Q{dv&D@tj1rO(CR&)|8<<~7%L`|Ybfi(@j3C!W~({;c4{1@DTVX{f}ir(T>k zt#D?an#-fVF3C}=f*GCNykCSktFb=y^*$-MU2NB(&n;&YJ+CLEM4oM0Yq~sl+Abq4 zTVt6>pGO~-d8ho0Jad=Ld~Rij=-xG(yTcyt@hw$0S#wcFxiS}s|w{+Q>HDMhWuKQ~K_gd|7CR1RiV0G1{h=}D!m{oIkoYa2wGVKbl|DSU@7fmv( zuHG}xwVJms>+RHL+w`ADH;7G{bFy;r2ECx==B}AKS7%uto1U*0o%{0J-yWmP*2^!Q z&Zle&G_m?P?d#(iDju&T*L-K%dX7i1{NUd*l88H?evdVypHbbubo!J z`mR}#%Vl}7fA#H_H#%Hd-vc%z=SXw&E=s-r<%-d+cEyVGksGBrAMJiFXdBslcTw;6 z2iLli_C72=Bstmfea73p9P4#vbY~~OTI=QI|0sXA>yo|RYY%Q$)!7!aM=UL0Vz<|a zHvtpaCZBfkJ+UwN-21)lg}i@%S8_~xRJelu@z1uJ|3B>d_V@4aJAeHB@$h{i`W;-U zc`4A;4P=4xHWw&|LNd9zkugXfENToF1!a9a8Qf$DXK;O&)UwRv)F5{!1?ONcBwlhc zS0R@ntbBl%9lCH9rW{-ajnC@kqq9~fro#a#NppmoW|9!nqtA+cm)2DjP* zE(nSw$Gp7!l46BuP}PO-Qb1xltaA2EP036owGsrGgs4wJ$pTi1JLl(>q~-x_1cd-h zFaT6nmnc|5g^lfg(sI85*D%1@d7r z+(v}YofAtEbMn(s^;Q(6rg0f47#eaxS^*$Z!OYau*i->14h0|!KtKVQL&3n%zyKHl zAb=!f1oRQAkf{NtkTEc`psKRK)M$yR%F+~A9HVG7GBg4PKbnvw!p4%K#LS%3A}&zy zdb$7uSED#TH&s(XBQr%462$sJ`T4-`1qB~CzVw4Lt5QK>0Sw&YlEk7CaOfBrnObtG Js=E5S0RUeO63zes literal 0 HcmV?d00001 diff --git a/fearless/Common/Configs/ApplicationConfigs.swift b/fearless/Common/Configs/ApplicationConfigs.swift index 2ef5cce7b..1762211e4 100644 --- a/fearless/Common/Configs/ApplicationConfigs.swift +++ b/fearless/Common/Configs/ApplicationConfigs.swift @@ -161,11 +161,11 @@ extension ApplicationConfig: ApplicationConfigProtocol, XcmConfigProtocol { // MARK: - GitHub var chainsSourceUrl: URL { -// #if F_DEV + #if F_DEV GitHubUrl.url(suffix: "chains/v10/chains_dev.json", branch: .developFree) -// #else -// GitHubUrl.url(suffix: "chains/v10/chains.json") -// #endif + #else + GitHubUrl.url(suffix: "chains/v10/chains.json") + #endif } var chainTypesSourceUrl: URL { diff --git a/fearless/Common/EventCenter/EventVisitor.swift b/fearless/Common/EventCenter/EventVisitor.swift index 51fce74e9..e4ec0d5f1 100644 --- a/fearless/Common/EventCenter/EventVisitor.swift +++ b/fearless/Common/EventCenter/EventVisitor.swift @@ -27,6 +27,7 @@ protocol EventVisitorProtocol: AnyObject { func processRemoteSubscriptionWasUpdated(event: WalletRemoteSubscriptionWasUpdatedEvent) func processChainsSetupCompleted() func processLogout() + func processAccountScoreSettingsChanged() } extension EventVisitorProtocol { @@ -56,4 +57,5 @@ extension EventVisitorProtocol { func processRemoteSubscriptionWasUpdated(event _: WalletRemoteSubscriptionWasUpdatedEvent) {} func processChainsSetupCompleted() {} func processLogout() {} + func processAccountScoreSettingsChanged() {} } diff --git a/fearless/Common/EventCenter/Events/AccountScoreSettingsChanged.swift b/fearless/Common/EventCenter/Events/AccountScoreSettingsChanged.swift new file mode 100644 index 000000000..76729592d --- /dev/null +++ b/fearless/Common/EventCenter/Events/AccountScoreSettingsChanged.swift @@ -0,0 +1,7 @@ +import Foundation + +struct AccountScoreSettingsChanged: EventProtocol { + func accept(visitor: EventVisitorProtocol) { + visitor.processAccountScoreSettingsChanged() + } +} diff --git a/fearless/Common/Extension/FWCosmosView.swift b/fearless/Common/Extension/FWCosmosView.swift new file mode 100644 index 000000000..45d5f759d --- /dev/null +++ b/fearless/Common/Extension/FWCosmosView.swift @@ -0,0 +1,28 @@ +import UIKit +import Cosmos + +class FWCosmosView: CosmosView { + override func point(inside point: CGPoint, with _: UIEvent?) -> Bool { + let oprimizedBounds = FWCosmosTouchTarget.optimize(bounds) + return oprimizedBounds.contains(point) + } +} + +enum FWCosmosTouchTarget { + static func optimize(_ bounds: CGRect) -> CGRect { + let recommendedHitSize: CGFloat = 60 + + var hitWidthIncrease: CGFloat = recommendedHitSize - bounds.width + var hitHeightIncrease: CGFloat = recommendedHitSize - bounds.height + + if hitWidthIncrease < 0 { hitWidthIncrease = 0 } + if hitHeightIncrease < 0 { hitHeightIncrease = 0 } + + let extendedBounds: CGRect = bounds.insetBy( + dx: -hitWidthIncrease / 2, + dy: -hitHeightIncrease / 2 + ) + + return extendedBounds + } +} diff --git a/fearless/Common/Extension/SettingsExtension.swift b/fearless/Common/Extension/SettingsExtension.swift index 7c247f311..0caebfbd3 100644 --- a/fearless/Common/Extension/SettingsExtension.swift +++ b/fearless/Common/Extension/SettingsExtension.swift @@ -14,6 +14,7 @@ enum SettingsKey: String { case referralEthereumAccount case selectedCurrency case shouldPlayAssetManagementAnimateKey + case accountScoreEnabled } extension SettingsManagerProtocol { @@ -91,4 +92,18 @@ extension SettingsManagerProtocol { set(value: newValue, for: SettingsKey.shouldPlayAssetManagementAnimateKey.rawValue) } } + + var accountScoreEnabled: Bool? { + get { + bool(for: SettingsKey.accountScoreEnabled.rawValue) ?? true + } + + set { + if let existingValue = newValue { + set(value: existingValue, for: SettingsKey.accountScoreEnabled.rawValue) + } else { + removeValue(for: SettingsKey.accountScoreEnabled.rawValue) + } + } + } } diff --git a/fearless/Common/View/AccountScore/AccountScoreView.swift b/fearless/Common/View/AccountScore/AccountScoreView.swift index c5a9556f5..e9e29ad22 100644 --- a/fearless/Common/View/AccountScore/AccountScoreView.swift +++ b/fearless/Common/View/AccountScore/AccountScoreView.swift @@ -7,8 +7,8 @@ class AccountScoreView: UIView { private var skeletonView: SkrullableView? - let starView: CosmosView = { - let view = CosmosView() + let starView: FWCosmosView = { + let view = FWCosmosView() view.settings.totalStars = 1 view.settings.starSize = 15 view.settings.textMargin = 2 @@ -31,12 +31,15 @@ class AccountScoreView: UIView { } func bind(viewModel: AccountScoreViewModel?) { + self.viewModel = viewModel + viewModel?.setup(with: self) + if viewModel?.scoringEnabled == false { isHidden = true return } - if viewModel?.address.starts(with: "0x") != true { + if viewModel?.address?.starts(with: "0x") != true { isHidden = false bindEmptyViewModel() return @@ -44,8 +47,6 @@ class AccountScoreView: UIView { isHidden = false startLoadingIfNeeded() - self.viewModel = viewModel - viewModel?.setup(with: self) } func bind(score: Int, rate: AccountScoreRate) { @@ -98,6 +99,11 @@ class AccountScoreView: UIView { super.layoutSubviews() didUpdateSkeletonLayout() } + + override func point(inside point: CGPoint, with _: UIEvent?) -> Bool { + let oprimizedBounds = FWCosmosTouchTarget.optimize(bounds) + return oprimizedBounds.contains(point) + } } extension AccountScoreView: SkeletonLoadable { diff --git a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift index 800b420b7..53906134a 100644 --- a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift +++ b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift @@ -1,4 +1,5 @@ import UIKit +import SoraKeystore import SSFModels enum AccountScoreRate { @@ -29,21 +30,39 @@ enum AccountScoreRate { } class AccountScoreViewModel { + private let eventCenter: EventCenterProtocol private let fetcher: AccountStatisticsFetching - let address: String - let scoringEnabled: Bool + private let chain: ChainModel? + private let settings: SettingsManagerProtocol + let address: String? + var scoringEnabled: Bool weak var view: AccountScoreView? - init(fetcher: AccountStatisticsFetching, address: String, chain: ChainModel?) { + init( + fetcher: AccountStatisticsFetching, + address: String?, + chain: ChainModel?, + settings: SettingsManagerProtocol, + eventCenter: EventCenterProtocol + ) { self.fetcher = fetcher self.address = address - scoringEnabled = chain?.isNomisSupported == true || chain == nil + self.chain = chain + self.settings = settings + self.eventCenter = eventCenter + + scoringEnabled = (chain?.isNomisSupported == true || chain == nil) && settings.accountScoreEnabled == true } func setup(with view: AccountScoreView?) { + eventCenter.add(observer: self) self.view = view + guard let address else { + return + } + Task { do { let stream = try await fetcher.subscribeForStatistics(address: address, cacheOptions: .onAll) @@ -70,3 +89,13 @@ class AccountScoreViewModel { } } } + +extension AccountScoreViewModel: EventVisitorProtocol { + func processAccountScoreSettingsChanged() { + scoringEnabled = (chain?.isNomisSupported == true || chain == nil) && settings.accountScoreEnabled == true + + DispatchQueue.main.async { [weak self] in + self?.view?.bind(viewModel: self) + } + } +} diff --git a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift deleted file mode 100644 index 6cf4606eb..000000000 --- a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift +++ /dev/null @@ -1,17 +0,0 @@ -// import Foundation -// -// protocol AccountScoreViewModelFactory { -// func buildViewModel(from accountStatistics: AccountStatistics) -> AccountScoreViewModel? -// } -// -// final class AccountScoreViewModelFactoryImpl: AccountScoreViewModelFactory { -// func buildViewModel(from accountStatistics: AccountStatistics) -> AccountScoreViewModel? { -// guard let score = accountStatistics.score else { -// return nil -// } -// -// let rate = AccountScoreRate(from: score) -// let intScore = ((score * 100.0) as NSDecimalNumber).intValue -// return AccountScoreViewModel(accountScoreLabelText: "\(intScore)", rate: rate) -// } -// } diff --git a/fearless/Modules/BackupWallet/BackupWalletAssembly.swift b/fearless/Modules/BackupWallet/BackupWalletAssembly.swift index 0d24dcafc..22a06bd5e 100644 --- a/fearless/Modules/BackupWallet/BackupWalletAssembly.swift +++ b/fearless/Modules/BackupWallet/BackupWalletAssembly.swift @@ -40,7 +40,7 @@ final class BackupWalletAssembly { signer: NomisRequestSigner() ) - let viewModelFactory = BackupWalletViewModelFactory(accountScoreFetcher: accountScoreFetcher) + let viewModelFactory = BackupWalletViewModelFactory(accountScoreFetcher: accountScoreFetcher, settings: SettingsManager.shared) let presenter = BackupWalletPresenter( wallet: wallet, interactor: interactor, diff --git a/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift b/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift index 40e849caf..c4f615d39 100644 --- a/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift +++ b/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift @@ -2,6 +2,7 @@ import Foundation import SoraFoundation import SSFCloudStorage import SSFModels +import SoraKeystore protocol BackupWalletViewModelFactoryProtocol { func createViewModel( @@ -35,9 +36,11 @@ enum BackupWalletOptions: Int, CaseIterable { final class BackupWalletViewModelFactory: BackupWalletViewModelFactoryProtocol { private lazy var assetBalanceFormatterFactory = AssetBalanceFormatterFactory() private let accountScoreFetcher: AccountStatisticsFetching + private let settings: SettingsManagerProtocol - init(accountScoreFetcher: AccountStatisticsFetching) { + init(accountScoreFetcher: AccountStatisticsFetching, settings: SettingsManagerProtocol) { self.accountScoreFetcher = accountScoreFetcher + self.settings = settings } func createViewModel( @@ -153,7 +156,7 @@ final class BackupWalletViewModelFactory: BackupWalletViewModelFactoryProtocol { locale: Locale ) -> WalletsManagmentCellViewModel { let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared) var fiatBalance: String = "" var dayChange: NSAttributedString? diff --git a/fearless/Modules/Contacts/ContactsAssembly.swift b/fearless/Modules/Contacts/ContactsAssembly.swift index a3db9f01c..4a4211a08 100644 --- a/fearless/Modules/Contacts/ContactsAssembly.swift +++ b/fearless/Modules/Contacts/ContactsAssembly.swift @@ -3,6 +3,7 @@ import SoraFoundation import RobinHood import SSFModels import SSFNetwork +import SoraKeystore enum ContactSource { case token(chainAsset: ChainAsset) @@ -63,7 +64,11 @@ enum ContactsAssembly { interactor: interactor, router: router, localizationManager: localizationManager, - viewModelFactory: AddressBookViewModelFactory(accountScoreFetcher: accountScoreFetcher, chain: source.chain), + viewModelFactory: AddressBookViewModelFactory( + accountScoreFetcher: accountScoreFetcher, + chain: source.chain, + settings: SettingsManager.shared + ), moduleOutput: moduleOutput, source: source, wallet: wallet diff --git a/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift b/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift index c53bf64f2..6491994ce 100644 --- a/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift +++ b/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift @@ -1,5 +1,6 @@ import Foundation import SSFModels +import SoraKeystore protocol AddressBookViewModelFactoryProtocol { func buildCellViewModels( @@ -18,13 +19,16 @@ struct ContactsTableSectionModel { final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { private let accountScoreFetcher: AccountStatisticsFetching private let chain: ChainModel + private let settings: SettingsManagerProtocol init( accountScoreFetcher: AccountStatisticsFetching, - chain: ChainModel + chain: ChainModel, + settings: SettingsManagerProtocol ) { self.accountScoreFetcher = accountScoreFetcher self.chain = chain + self.settings = settings } func buildCellViewModels( @@ -38,7 +42,9 @@ final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { let accountScoreViewModel = AccountScoreViewModel( fetcher: accountScoreFetcher, address: contactType.address, - chain: chain + chain: chain, + settings: settings, + eventCenter: EventCenter.shared ) return ContactTableCellModel( @@ -66,7 +72,9 @@ final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { let accountScoreViewModel = AccountScoreViewModel( fetcher: accountScoreFetcher, address: contact.address, - chain: chain + chain: chain, + settings: settings, + eventCenter: EventCenter.shared ) return ContactTableCellModel(contactType: .saved(contact), delegate: cellsDelegate, accountScoreViewModel: accountScoreViewModel) diff --git a/fearless/Modules/NFT/NftSend/NftSendAssembly.swift b/fearless/Modules/NFT/NftSend/NftSendAssembly.swift index f73695b99..82c7b6f4c 100644 --- a/fearless/Modules/NFT/NftSend/NftSendAssembly.swift +++ b/fearless/Modules/NFT/NftSend/NftSendAssembly.swift @@ -71,7 +71,8 @@ enum NftSendAssembly { viewModelFactory: SendViewModelFactory( iconGenerator: UniversalIconGenerator(), - accountScoreFetcher: accountStatisticsFetcher + accountScoreFetcher: accountStatisticsFetcher, + settings: SettingsManager.shared ), dataValidatingFactory: dataValidatingFactory ) diff --git a/fearless/Modules/Profile/ProfilePresenter.swift b/fearless/Modules/Profile/ProfilePresenter.swift index 9a1c62742..815577472 100644 --- a/fearless/Modules/Profile/ProfilePresenter.swift +++ b/fearless/Modules/Profile/ProfilePresenter.swift @@ -90,7 +90,7 @@ extension ProfilePresenter: ProfilePresenterProtocol { case .currency: guard let selectedWallet = selectedWallet else { return } wireframe.showSelectCurrency(from: view, with: selectedWallet) - case .biometry: + case .biometry, .accountScore: break case .walletConnect: wireframe.showWalletConnect(from: view) @@ -102,6 +102,11 @@ extension ProfilePresenter: ProfilePresenterProtocol { switch option { case .biometry: settings.biometryEnabled = isOn + case .accountScore: + settings.accountScoreEnabled = isOn + + let event = AccountScoreSettingsChanged() + eventCenter.notify(with: event) default: break } diff --git a/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift b/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift index cfc925059..dc9262475 100644 --- a/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift +++ b/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift @@ -25,6 +25,7 @@ enum ProfileOption: UInt, CaseIterable { case changePincode case biometry case about + case accountScore } final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { @@ -108,7 +109,7 @@ final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { } let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared) return WalletsManagmentCellViewModel( isSelected: false, @@ -146,6 +147,8 @@ final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { return createBiometryViewModel() case .currency: return createCurrencyViewModel(from: currency, locale: locale) + case .accountScore: + return createAccountScoreViewModel(locale: locale) } } @@ -289,6 +292,20 @@ final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { return viewModel } + private func createAccountScoreViewModel(locale: Locale) -> ProfileOptionViewModel? { + let viewModel = ProfileOptionViewModel( + title: R.string.localizable.profileAccountScoreTitle(preferredLanguages: locale.rLanguages), + icon: R.image.iconProfleAccountScore(), + accessoryTitle: nil, + accessoryImage: nil, + accessoryType: .switcher(settings.accountScoreEnabled ?? false), + option: .accountScore + ) + return viewModel + } + + // MARK: Additional + private func getDayChangeAttributedString( currency: Currency, dayChange: Decimal, diff --git a/fearless/Modules/Send/SendAssembly.swift b/fearless/Modules/Send/SendAssembly.swift index 35d820a6f..19cebe8a6 100644 --- a/fearless/Modules/Send/SendAssembly.swift +++ b/fearless/Modules/Send/SendAssembly.swift @@ -12,6 +12,7 @@ import SSFExtrinsicKit import SSFNetwork import SSFChainRegistry import SSFChainConnection +import SoraKeystore final class SendAssembly { static func configureModule( @@ -77,7 +78,8 @@ final class SendAssembly { let viewModelFactory = SendViewModelFactory( iconGenerator: UniversalIconGenerator(), - accountScoreFetcher: accountStatisticsFetcher + accountScoreFetcher: accountStatisticsFetcher, + settings: SettingsManager.shared ) let dataValidatingFactory = SendDataValidatingFactory(presentable: router) let presenter = SendPresenter( diff --git a/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift b/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift index ae605643e..1793a4d7a 100644 --- a/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift +++ b/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift @@ -1,5 +1,6 @@ import SSFUtils import SSFModels +import SoraKeystore protocol SendViewModelFactoryProtocol { func buildRecipientViewModel( @@ -14,13 +15,16 @@ protocol SendViewModelFactoryProtocol { final class SendViewModelFactory: SendViewModelFactoryProtocol { private let iconGenerator: IconGenerating private let accountScoreFetcher: AccountStatisticsFetching + private let settings: SettingsManagerProtocol init( iconGenerator: IconGenerating, - accountScoreFetcher: AccountStatisticsFetching + accountScoreFetcher: AccountStatisticsFetching, + settings: SettingsManagerProtocol ) { self.iconGenerator = iconGenerator self.accountScoreFetcher = accountScoreFetcher + self.settings = settings } func buildRecipientViewModel( @@ -46,6 +50,12 @@ final class SendViewModelFactory: SendViewModelFactoryProtocol { } func buildAccountScoreViewModel(address: String, chain: ChainModel) -> AccountScoreViewModel? { - AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: chain) + AccountScoreViewModel( + fetcher: accountScoreFetcher, + address: address, + chain: chain, + settings: settings, + eventCenter: EventCenter.shared + ) } } diff --git a/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift b/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift index 7e9ff9f47..95b2c3b79 100644 --- a/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift +++ b/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift @@ -4,6 +4,7 @@ import SoraFoundation import SoraUI import RobinHood import SSFNetwork +import SoraKeystore enum WalletConnectSessionAssembly { static func configureModule( @@ -45,7 +46,8 @@ enum WalletConnectSessionAssembly { walletConnectModelFactory: walletConnectModelFactory, walletConnectPayloaFactory: walletConnectPayloaFactory, assetBalanceFormatterFactory: AssetBalanceFormatterFactory(), - accountScoreFetcher: accountScoreFetcher + accountScoreFetcher: accountScoreFetcher, + settings: SettingsManager.shared ) let presenter = WalletConnectSessionPresenter( request: request, diff --git a/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift b/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift index ebf674d7c..46883f94d 100644 --- a/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift +++ b/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift @@ -3,6 +3,7 @@ import WalletConnectSign import SoraFoundation import SSFModels import SSFUtils +import SoraKeystore protocol WalletConnectSessionViewModelFactory { func buildViewModel( @@ -20,6 +21,7 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo private let walletConnectPayloaFactory: WalletConnectPayloadFactory private let assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol private let accountScoreFetcher: AccountStatisticsFetching + private let settings: SettingsManagerProtocol init( request: Request, @@ -27,7 +29,8 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo walletConnectModelFactory: WalletConnectModelFactory, walletConnectPayloaFactory: WalletConnectPayloadFactory, assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol, - accountScoreFetcher: AccountStatisticsFetching + accountScoreFetcher: AccountStatisticsFetching, + settings: SettingsManagerProtocol ) { self.request = request self.session = session @@ -35,6 +38,7 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo self.walletConnectPayloaFactory = walletConnectPayloaFactory self.assetBalanceFormatterFactory = assetBalanceFormatterFactory self.accountScoreFetcher = accountScoreFetcher + self.settings = settings } func buildViewModel( @@ -106,7 +110,7 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo locale: Locale ) -> WalletsManagmentCellViewModel { let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared) guard let balance = balanceInfo?[wallet.metaId] else { return WalletsManagmentCellViewModel( diff --git a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift index 6abe19930..6e544f02b 100644 --- a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift +++ b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift @@ -1,5 +1,6 @@ import Foundation import SSFModels +import SoraKeystore protocol WalletMainContainerViewModelFactoryProtocol { func buildViewModel( @@ -12,9 +13,11 @@ protocol WalletMainContainerViewModelFactoryProtocol { final class WalletMainContainerViewModelFactory: WalletMainContainerViewModelFactoryProtocol { private let accountScoreFetcher: AccountStatisticsFetching + private let settings: SettingsManagerProtocol - init(accountScoreFetcher: AccountStatisticsFetching) { + init(accountScoreFetcher: AccountStatisticsFetching, settings: SettingsManagerProtocol) { self.accountScoreFetcher = accountScoreFetcher + self.settings = settings } func buildViewModel( @@ -53,7 +56,7 @@ final class WalletMainContainerViewModelFactory: WalletMainContainerViewModelFac } let ethAddress = selectedMetaAccount.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = ethAddress.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: ethAddress, chain: nil, settings: settings, eventCenter: EventCenter.shared) return WalletMainContainerViewModel( walletName: selectedMetaAccount.name, diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift index 218e18d49..74bda5c48 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift @@ -3,6 +3,7 @@ import SoraFoundation import RobinHood import SSFUtils import SSFNetwork +import SoraKeystore final class WalletMainContainerAssembly { static func configureModule( @@ -82,7 +83,10 @@ final class WalletMainContainerAssembly { return nil } - let viewModelFactory = WalletMainContainerViewModelFactory(accountScoreFetcher: accountScoreFetcher) + let viewModelFactory = WalletMainContainerViewModelFactory( + accountScoreFetcher: accountScoreFetcher, + settings: SettingsManager.shared + ) let presenter = WalletMainContainerPresenter( balanceInfoModuleInput: balanceInfoModule.input, assetListModuleInput: assetListModule.input, diff --git a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift index 64faa7da3..0a67cbe1a 100644 --- a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift +++ b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift @@ -1,6 +1,7 @@ import Foundation import SoraFoundation import SSFModels +import SoraKeystore protocol WalletsManagmentViewModelFactoryProtocol { func buildViewModel( @@ -41,7 +42,13 @@ final class WalletsManagmentViewModelFactory: WalletsManagmentViewModelFactoryPr } let address = managedMetaAccount.info.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } + let accountScoreViewModel = AccountScoreViewModel( + fetcher: accountScoreFetcher, + address: address, + chain: nil, + settings: SettingsManager.shared, + eventCenter: EventCenter.shared + ) guard let walletBalance = balances[key] else { return WalletsManagmentCellViewModel( diff --git a/fearless/en.lproj/Localizable.strings b/fearless/en.lproj/Localizable.strings index e8311baac..f4dc6422c 100644 --- a/fearless/en.lproj/Localizable.strings +++ b/fearless/en.lproj/Localizable.strings @@ -1207,3 +1207,4 @@ belongs to the right network"; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/id.lproj/Localizable.strings b/fearless/id.lproj/Localizable.strings index 6ce9c6b71..34dcfa39a 100644 --- a/fearless/id.lproj/Localizable.strings +++ b/fearless/id.lproj/Localizable.strings @@ -1191,3 +1191,4 @@ akan muncul di sini"; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/ja.lproj/Localizable.strings b/fearless/ja.lproj/Localizable.strings index c28fc6a24..eb96913b2 100644 --- a/fearless/ja.lproj/Localizable.strings +++ b/fearless/ja.lproj/Localizable.strings @@ -1197,3 +1197,4 @@ "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/pt-PT.lproj/Localizable.strings b/fearless/pt-PT.lproj/Localizable.strings index 33be92614..d8fe6cd6b 100644 --- a/fearless/pt-PT.lproj/Localizable.strings +++ b/fearless/pt-PT.lproj/Localizable.strings @@ -1283,3 +1283,4 @@ To find out more, contact %@"; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/ru.lproj/Localizable.strings b/fearless/ru.lproj/Localizable.strings index f7ff56506..8cc5cf515 100644 --- a/fearless/ru.lproj/Localizable.strings +++ b/fearless/ru.lproj/Localizable.strings @@ -1205,3 +1205,4 @@ Euro cash"; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/tr.lproj/Localizable.strings b/fearless/tr.lproj/Localizable.strings index e11725bfc..682e757fd 100644 --- a/fearless/tr.lproj/Localizable.strings +++ b/fearless/tr.lproj/Localizable.strings @@ -1203,3 +1203,4 @@ ait olduğundan emin olun."; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/vi.lproj/Localizable.strings b/fearless/vi.lproj/Localizable.strings index f7adc1aee..de60ae156 100644 --- a/fearless/vi.lproj/Localizable.strings +++ b/fearless/vi.lproj/Localizable.strings @@ -1205,3 +1205,4 @@ thuộc đúng mạng"; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/zh-Hans.lproj/Localizable.strings b/fearless/zh-Hans.lproj/Localizable.strings index f3b101350..cf73224e5 100644 --- a/fearless/zh-Hans.lproj/Localizable.strings +++ b/fearless/zh-Hans.lproj/Localizable.strings @@ -1202,3 +1202,4 @@ "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; From fd4449b4799262cb22341d13b9ad20904ee887d4 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Thu, 1 Aug 2024 16:22:22 +0700 Subject: [PATCH 13/18] self codereview fix --- fearless/Common/Extension/FWCosmosView.swift | 2 +- .../Services/ChainRegistry/ChainRegistry.swift | 13 ++++--------- .../PolkaswapAdjustmentAssembly.swift | 2 +- .../WalletMainContainerViewLayout.swift | 2 +- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/fearless/Common/Extension/FWCosmosView.swift b/fearless/Common/Extension/FWCosmosView.swift index 45d5f759d..42f7f5b78 100644 --- a/fearless/Common/Extension/FWCosmosView.swift +++ b/fearless/Common/Extension/FWCosmosView.swift @@ -10,7 +10,7 @@ class FWCosmosView: CosmosView { enum FWCosmosTouchTarget { static func optimize(_ bounds: CGRect) -> CGRect { - let recommendedHitSize: CGFloat = 60 + let recommendedHitSize: CGFloat = 44 var hitWidthIncrease: CGFloat = recommendedHitSize - bounds.width var hitHeightIncrease: CGFloat = recommendedHitSize - bounds.height diff --git a/fearless/Common/Services/ChainRegistry/ChainRegistry.swift b/fearless/Common/Services/ChainRegistry/ChainRegistry.swift index 531d6ead7..0478c9660 100644 --- a/fearless/Common/Services/ChainRegistry/ChainRegistry.swift +++ b/fearless/Common/Services/ChainRegistry/ChainRegistry.swift @@ -228,21 +228,16 @@ final class ChainRegistry { } chains.append(newChain) - DispatchQueue.global(qos: .background).async { - _ = try? ethereumConnectionPool.setupConnection(for: newChain) - } + _ = try? ethereumConnectionPool.setupConnection(for: newChain) } private func handleUpdatedEthereumChain(updatedChain: ChainModel) throws { guard let ethereumConnectionPool = self.ethereumConnectionPool else { return } - DispatchQueue.global(qos: .background).async { [weak self] in - guard let self else { return } - _ = try? ethereumConnectionPool.setupConnection(for: updatedChain) - self.chains = self.chains.filter { $0.chainId != updatedChain.chainId } - self.chains.append(updatedChain) - } + _ = try? ethereumConnectionPool.setupConnection(for: updatedChain) + chains = chains.filter { $0.chainId != updatedChain.chainId } + chains.append(updatedChain) } private func handleDeletedEthereumChain(chainId: ChainModel.Id) { diff --git a/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift b/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift index df32ea703..9f7a06d7a 100644 --- a/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift +++ b/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift @@ -14,7 +14,7 @@ final class PolkaswapAdjustmentAssembly { let chainRegistry = ChainRegistryFacade.sharedRegistry guard - let xorChainAsset = chainRegistry.getChainUnsafe(for: Chain.soraMain.genesisHash)?.utilityChainAssets().first, + let xorChainAsset = chainRegistry.getChain(for: Chain.soraMain.genesisHash)?.utilityChainAssets().first, let connection = chainRegistry.getConnection(for: xorChainAsset.chain.chainId), let accountResponse = wallet.fetch(for: xorChainAsset.chain.accountRequest()), let runtimeService = chainRegistry.getRuntimeProvider(for: xorChainAsset.chain.chainId) diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift index f31fef863..7ecf8701d 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift @@ -212,7 +212,7 @@ final class WalletMainContainerViewLayout: UIView { } private func setupWalletBalanceLayout() { - addSubview(accountScoreView) + insertSubview(accountScoreView, belowSubview: navigationContainerView) accountScoreView.snp.makeConstraints { make in make.top.equalTo(navigationContainerView.snp.bottom).offset(4) make.centerX.equalTo(switchWalletButton.snp.centerX) From d0d544cbe284b4ac5baa1e734aabc0653655ede8 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Tue, 30 Jul 2024 15:18:03 +0700 Subject: [PATCH 14/18] dev config --- fearless/Common/Configs/ApplicationConfigs.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fearless/Common/Configs/ApplicationConfigs.swift b/fearless/Common/Configs/ApplicationConfigs.swift index 1762211e4..2ef5cce7b 100644 --- a/fearless/Common/Configs/ApplicationConfigs.swift +++ b/fearless/Common/Configs/ApplicationConfigs.swift @@ -161,11 +161,11 @@ extension ApplicationConfig: ApplicationConfigProtocol, XcmConfigProtocol { // MARK: - GitHub var chainsSourceUrl: URL { - #if F_DEV +// #if F_DEV GitHubUrl.url(suffix: "chains/v10/chains_dev.json", branch: .developFree) - #else - GitHubUrl.url(suffix: "chains/v10/chains.json") - #endif +// #else +// GitHubUrl.url(suffix: "chains/v10/chains.json") +// #endif } var chainTypesSourceUrl: URL { From 15345aae0aa452d366344976d42634c00c7a8d19 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Wed, 31 Jul 2024 15:36:24 +0700 Subject: [PATCH 15/18] nomis fixes: toggle for visible management, touch location increased --- fearless.xcodeproj/project.pbxproj | 12 ++++-- .../Contents.json | 12 ++++++ .../iconProfleAccountScore.imageset/Star.pdf | Bin 0 -> 2560 bytes .../Common/Configs/ApplicationConfigs.swift | 8 ++-- .../Common/EventCenter/EventVisitor.swift | 2 + .../Events/AccountScoreSettingsChanged.swift | 7 ++++ fearless/Common/Extension/FWCosmosView.swift | 28 +++++++++++++ .../Common/Extension/SettingsExtension.swift | 15 +++++++ .../View/AccountScore/AccountScoreView.swift | 16 +++++--- .../AccountScore/AccountScoreViewModel.swift | 37 ++++++++++++++++-- .../AccountScoreViewModelFactory.swift | 17 -------- .../BackupWallet/BackupWalletAssembly.swift | 2 +- .../BackupWalletViewModelFactory.swift | 7 +++- .../Modules/Contacts/ContactsAssembly.swift | 7 +++- .../AddressBookViewModelFactory.swift | 14 +++++-- .../Modules/NFT/NftSend/NftSendAssembly.swift | 3 +- .../Modules/Profile/ProfilePresenter.swift | 7 +++- .../ViewModel/ProfileViewModelFactory.swift | 19 ++++++++- fearless/Modules/Send/SendAssembly.swift | 4 +- .../Send/ViewModel/SendViewModelFactory.swift | 14 ++++++- .../WalletConnectSessionAssembly.swift | 4 +- ...WalletConnectSessionViewModelFactory.swift | 8 +++- .../WalletMainContainerViewModelFactory.swift | 7 +++- .../WalletMainContainerAssembly.swift | 6 ++- .../WalletsManagmentViewModelFactory.swift | 9 ++++- fearless/en.lproj/Localizable.strings | 1 + fearless/id.lproj/Localizable.strings | 1 + fearless/ja.lproj/Localizable.strings | 1 + fearless/pt-PT.lproj/Localizable.strings | 1 + fearless/ru.lproj/Localizable.strings | 1 + fearless/tr.lproj/Localizable.strings | 1 + fearless/vi.lproj/Localizable.strings | 1 + fearless/zh-Hans.lproj/Localizable.strings | 1 + 33 files changed, 219 insertions(+), 54 deletions(-) create mode 100644 fearless/Assets.xcassets/iconProfleAccountScore.imageset/Contents.json create mode 100644 fearless/Assets.xcassets/iconProfleAccountScore.imageset/Star.pdf create mode 100644 fearless/Common/EventCenter/Events/AccountScoreSettingsChanged.swift create mode 100644 fearless/Common/Extension/FWCosmosView.swift delete mode 100644 fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift diff --git a/fearless.xcodeproj/project.pbxproj b/fearless.xcodeproj/project.pbxproj index 4b93ab376..0469f366c 100644 --- a/fearless.xcodeproj/project.pbxproj +++ b/fearless.xcodeproj/project.pbxproj @@ -3042,7 +3042,6 @@ FAF600752C48D79600E56558 /* Cosmos in Frameworks */ = {isa = PBXBuildFile; productRef = FAF600742C48D79600E56558 /* Cosmos */; }; FAF600772C48F08B00E56558 /* AccountScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF600762C48F08B00E56558 /* AccountScoreView.swift */; }; FAF6007A2C48F12000E56558 /* AccountScoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */; }; - FAF6007C2C48FC2500E56558 /* AccountScoreViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */; }; FAF6D90D2C57654F00274E69 /* Decimal+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF6D90C2C57654F00274E69 /* Decimal+Formatting.swift */; }; FAF92E6627B4275F005467CE /* Bool+ToInt.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF92E6527B4275E005467CE /* Bool+ToInt.swift */; }; FAF96B582B636FC700E299C1 /* SystemNumberRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF96B572B636FC700E299C1 /* SystemNumberRequest.swift */; }; @@ -3059,6 +3058,8 @@ FAF9C2B42AAF3FDF00A61D21 /* GetPreinstalledWalletProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF9C2AD2AAF3FDF00A61D21 /* GetPreinstalledWalletProtocols.swift */; }; FAF9C2B72AAF3FF100A61D21 /* GetPreinstalledWalletTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF9C2B62AAF3FF100A61D21 /* GetPreinstalledWalletTests.swift */; }; FAFB47D72ABD589C0008F8CA /* EthereumBalanceRepositoryCacheWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFB47D62ABD589C0008F8CA /* EthereumBalanceRepositoryCacheWrapper.swift */; }; + FAFB5EE02C5A11A30015D3DD /* AccountScoreSettingsChanged.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFB5EDF2C5A11A30015D3DD /* AccountScoreSettingsChanged.swift */; }; + FAFB5EE22C5A2CE80015D3DD /* FWCosmosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFB5EE12C5A2CE80015D3DD /* FWCosmosView.swift */; }; FAFBEE81284621800036D08C /* SelectedValidatorListParachainViewModelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFBEE80284621800036D08C /* SelectedValidatorListParachainViewModelState.swift */; }; FAFBEE83284621900036D08C /* SelectedValidatorListParachainViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFBEE82284621900036D08C /* SelectedValidatorListParachainViewModelFactory.swift */; }; FAFDB2C329112A00003971FB /* SubstrateCallPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFDB2C229112A00003971FB /* SubstrateCallPath.swift */; }; @@ -6179,7 +6180,6 @@ FAF5E9E027E4A4C1005A3448 /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = ""; }; FAF600762C48F08B00E56558 /* AccountScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreView.swift; sourceTree = ""; }; FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreViewModel.swift; sourceTree = ""; }; - FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreViewModelFactory.swift; sourceTree = ""; }; FAF6D90C2C57654F00274E69 /* Decimal+Formatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+Formatting.swift"; sourceTree = ""; }; FAF92E6527B4275E005467CE /* Bool+ToInt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bool+ToInt.swift"; sourceTree = ""; }; FAF96B572B636FC700E299C1 /* SystemNumberRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemNumberRequest.swift; sourceTree = ""; }; @@ -6202,6 +6202,8 @@ FAF9C2AD2AAF3FDF00A61D21 /* GetPreinstalledWalletProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPreinstalledWalletProtocols.swift; sourceTree = ""; }; FAF9C2B62AAF3FF100A61D21 /* GetPreinstalledWalletTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPreinstalledWalletTests.swift; sourceTree = ""; }; FAFB47D62ABD589C0008F8CA /* EthereumBalanceRepositoryCacheWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumBalanceRepositoryCacheWrapper.swift; sourceTree = ""; }; + FAFB5EDF2C5A11A30015D3DD /* AccountScoreSettingsChanged.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountScoreSettingsChanged.swift; sourceTree = ""; }; + FAFB5EE12C5A2CE80015D3DD /* FWCosmosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FWCosmosView.swift; sourceTree = ""; }; FAFBEE80284621800036D08C /* SelectedValidatorListParachainViewModelState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedValidatorListParachainViewModelState.swift; sourceTree = ""; }; FAFBEE82284621900036D08C /* SelectedValidatorListParachainViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedValidatorListParachainViewModelFactory.swift; sourceTree = ""; }; FAFDB2C229112A00003971FB /* SubstrateCallPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubstrateCallPath.swift; sourceTree = ""; }; @@ -9398,6 +9400,7 @@ FAC6CDA82BA814F20013A17E /* UIControl+Disable.swift */, FAF6D90C2C57654F00274E69 /* Decimal+Formatting.swift */, FAB482F02C58AC7F00594D89 /* ChainModel+Nomis.swift */, + FAFB5EE12C5A2CE80015D3DD /* FWCosmosView.swift */, ); path = Extension; sourceTree = ""; @@ -10688,6 +10691,7 @@ FA37AE4C28603C37001DCA96 /* StakingUpdatedEvent.swift */, FAE39AF22A9E1A4F0011A9D6 /* ChainsSetupCompleted.swift */, C65C7F6A2AD82B8D0069D877 /* LogoutEvent.swift */, + FAFB5EDF2C5A11A30015D3DD /* AccountScoreSettingsChanged.swift */, ); path = Events; sourceTree = ""; @@ -15590,7 +15594,6 @@ isa = PBXGroup; children = ( FAF600792C48F12000E56558 /* AccountScoreViewModel.swift */, - FAF6007B2C48FC2500E56558 /* AccountScoreViewModelFactory.swift */, ); path = AccountScore; sourceTree = ""; @@ -18326,6 +18329,7 @@ FAF9C2B22AAF3FDF00A61D21 /* GetPreinstalledWalletPresenter.swift in Sources */, 90EFE3768F1375470FDBE6F6 /* PurchaseViewFactory.swift in Sources */, F4A198EE2631AA2000CD6E61 /* StakingBalanceUnbondingItemView.swift in Sources */, + FAFB5EE22C5A2CE80015D3DD /* FWCosmosView.swift in Sources */, C63468E228F2C964005CB1F1 /* ContactTableCell.swift in Sources */, 076D9D57294B38E1002762E3 /* PolkaswapPreviewParams.swift in Sources */, FA99425528053C8800D771E5 /* SelectExportAccountInteractor.swift in Sources */, @@ -18513,6 +18517,7 @@ FAADC1A529261F7000DA9903 /* PoolRolesConfirmProtocols.swift in Sources */, F022F1444E0F75CCA42F4648 /* YourValidatorListProtocols.swift in Sources */, 8493D0E326FF571D00A28008 /* PriceProviderFactory.swift in Sources */, + FAFB5EE02C5A11A30015D3DD /* AccountScoreSettingsChanged.swift in Sources */, F7EB8F835CFA7FC949EF4C22 /* YourValidatorListWireframe.swift in Sources */, AEA0C8AC267B6B5200F9666F /* SelectedValidatorListViewFactory.swift in Sources */, DBA436B3B1C90965FE8F9B79 /* YourValidatorListPresenter.swift in Sources */, @@ -18956,7 +18961,6 @@ B15D513381B7626AB90018F0 /* StakingPoolInfoInteractor.swift in Sources */, 89C8A9B990B08016A70ED336 /* StakingPoolInfoViewController.swift in Sources */, 281D17EF8C45A1FC49FD1523 /* StakingPoolInfoViewLayout.swift in Sources */, - FAF6007C2C48FC2500E56558 /* AccountScoreViewModelFactory.swift in Sources */, 39DA5795FB9DBF626B72B5C6 /* StakingPoolInfoAssembly.swift in Sources */, 4A63ECA587C601999AAEB974 /* StakingPoolCreateProtocols.swift in Sources */, 7BC6FB48B9B4EC790923FF1E /* StakingPoolCreateRouter.swift in Sources */, diff --git a/fearless/Assets.xcassets/iconProfleAccountScore.imageset/Contents.json b/fearless/Assets.xcassets/iconProfleAccountScore.imageset/Contents.json new file mode 100644 index 000000000..66ce12636 --- /dev/null +++ b/fearless/Assets.xcassets/iconProfleAccountScore.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Star.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/fearless/Assets.xcassets/iconProfleAccountScore.imageset/Star.pdf b/fearless/Assets.xcassets/iconProfleAccountScore.imageset/Star.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ed199259af094a301b24875b7f4198ac911fb820 GIT binary patch literal 2560 zcmY!laB!u zh5UTJ%$HyH)=d*@&Yu@AH@`n4?e6i0sEB7)_6L=Ygd`f3e5rGjKK(2vdY2t@YPlop zrRfp#`WKrQZw&QG)qR@?Z(>dTrF+s?{d zTkWH`Q{dv&D@tj1rO(CR&)|8<<~7%L`|Ybfi(@j3C!W~({;c4{1@DTVX{f}ir(T>k zt#D?an#-fVF3C}=f*GCNykCSktFb=y^*$-MU2NB(&n;&YJ+CLEM4oM0Yq~sl+Abq4 zTVt6>pGO~-d8ho0Jad=Ld~Rij=-xG(yTcyt@hw$0S#wcFxiS}s|w{+Q>HDMhWuKQ~K_gd|7CR1RiV0G1{h=}D!m{oIkoYa2wGVKbl|DSU@7fmv( zuHG}xwVJms>+RHL+w`ADH;7G{bFy;r2ECx==B}AKS7%uto1U*0o%{0J-yWmP*2^!Q z&Zle&G_m?P?d#(iDju&T*L-K%dX7i1{NUd*l88H?evdVypHbbubo!J z`mR}#%Vl}7fA#H_H#%Hd-vc%z=SXw&E=s-r<%-d+cEyVGksGBrAMJiFXdBslcTw;6 z2iLli_C72=Bstmfea73p9P4#vbY~~OTI=QI|0sXA>yo|RYY%Q$)!7!aM=UL0Vz<|a zHvtpaCZBfkJ+UwN-21)lg}i@%S8_~xRJelu@z1uJ|3B>d_V@4aJAeHB@$h{i`W;-U zc`4A;4P=4xHWw&|LNd9zkugXfENToF1!a9a8Qf$DXK;O&)UwRv)F5{!1?ONcBwlhc zS0R@ntbBl%9lCH9rW{-ajnC@kqq9~fro#a#NppmoW|9!nqtA+cm)2DjP* zE(nSw$Gp7!l46BuP}PO-Qb1xltaA2EP036owGsrGgs4wJ$pTi1JLl(>q~-x_1cd-h zFaT6nmnc|5g^lfg(sI85*D%1@d7r z+(v}YofAtEbMn(s^;Q(6rg0f47#eaxS^*$Z!OYau*i->14h0|!KtKVQL&3n%zyKHl zAb=!f1oRQAkf{NtkTEc`psKRK)M$yR%F+~A9HVG7GBg4PKbnvw!p4%K#LS%3A}&zy zdb$7uSED#TH&s(XBQr%462$sJ`T4-`1qB~CzVw4Lt5QK>0Sw&YlEk7CaOfBrnObtG Js=E5S0RUeO63zes literal 0 HcmV?d00001 diff --git a/fearless/Common/Configs/ApplicationConfigs.swift b/fearless/Common/Configs/ApplicationConfigs.swift index 2ef5cce7b..1762211e4 100644 --- a/fearless/Common/Configs/ApplicationConfigs.swift +++ b/fearless/Common/Configs/ApplicationConfigs.swift @@ -161,11 +161,11 @@ extension ApplicationConfig: ApplicationConfigProtocol, XcmConfigProtocol { // MARK: - GitHub var chainsSourceUrl: URL { -// #if F_DEV + #if F_DEV GitHubUrl.url(suffix: "chains/v10/chains_dev.json", branch: .developFree) -// #else -// GitHubUrl.url(suffix: "chains/v10/chains.json") -// #endif + #else + GitHubUrl.url(suffix: "chains/v10/chains.json") + #endif } var chainTypesSourceUrl: URL { diff --git a/fearless/Common/EventCenter/EventVisitor.swift b/fearless/Common/EventCenter/EventVisitor.swift index 51fce74e9..e4ec0d5f1 100644 --- a/fearless/Common/EventCenter/EventVisitor.swift +++ b/fearless/Common/EventCenter/EventVisitor.swift @@ -27,6 +27,7 @@ protocol EventVisitorProtocol: AnyObject { func processRemoteSubscriptionWasUpdated(event: WalletRemoteSubscriptionWasUpdatedEvent) func processChainsSetupCompleted() func processLogout() + func processAccountScoreSettingsChanged() } extension EventVisitorProtocol { @@ -56,4 +57,5 @@ extension EventVisitorProtocol { func processRemoteSubscriptionWasUpdated(event _: WalletRemoteSubscriptionWasUpdatedEvent) {} func processChainsSetupCompleted() {} func processLogout() {} + func processAccountScoreSettingsChanged() {} } diff --git a/fearless/Common/EventCenter/Events/AccountScoreSettingsChanged.swift b/fearless/Common/EventCenter/Events/AccountScoreSettingsChanged.swift new file mode 100644 index 000000000..76729592d --- /dev/null +++ b/fearless/Common/EventCenter/Events/AccountScoreSettingsChanged.swift @@ -0,0 +1,7 @@ +import Foundation + +struct AccountScoreSettingsChanged: EventProtocol { + func accept(visitor: EventVisitorProtocol) { + visitor.processAccountScoreSettingsChanged() + } +} diff --git a/fearless/Common/Extension/FWCosmosView.swift b/fearless/Common/Extension/FWCosmosView.swift new file mode 100644 index 000000000..45d5f759d --- /dev/null +++ b/fearless/Common/Extension/FWCosmosView.swift @@ -0,0 +1,28 @@ +import UIKit +import Cosmos + +class FWCosmosView: CosmosView { + override func point(inside point: CGPoint, with _: UIEvent?) -> Bool { + let oprimizedBounds = FWCosmosTouchTarget.optimize(bounds) + return oprimizedBounds.contains(point) + } +} + +enum FWCosmosTouchTarget { + static func optimize(_ bounds: CGRect) -> CGRect { + let recommendedHitSize: CGFloat = 60 + + var hitWidthIncrease: CGFloat = recommendedHitSize - bounds.width + var hitHeightIncrease: CGFloat = recommendedHitSize - bounds.height + + if hitWidthIncrease < 0 { hitWidthIncrease = 0 } + if hitHeightIncrease < 0 { hitHeightIncrease = 0 } + + let extendedBounds: CGRect = bounds.insetBy( + dx: -hitWidthIncrease / 2, + dy: -hitHeightIncrease / 2 + ) + + return extendedBounds + } +} diff --git a/fearless/Common/Extension/SettingsExtension.swift b/fearless/Common/Extension/SettingsExtension.swift index 7c247f311..0caebfbd3 100644 --- a/fearless/Common/Extension/SettingsExtension.swift +++ b/fearless/Common/Extension/SettingsExtension.swift @@ -14,6 +14,7 @@ enum SettingsKey: String { case referralEthereumAccount case selectedCurrency case shouldPlayAssetManagementAnimateKey + case accountScoreEnabled } extension SettingsManagerProtocol { @@ -91,4 +92,18 @@ extension SettingsManagerProtocol { set(value: newValue, for: SettingsKey.shouldPlayAssetManagementAnimateKey.rawValue) } } + + var accountScoreEnabled: Bool? { + get { + bool(for: SettingsKey.accountScoreEnabled.rawValue) ?? true + } + + set { + if let existingValue = newValue { + set(value: existingValue, for: SettingsKey.accountScoreEnabled.rawValue) + } else { + removeValue(for: SettingsKey.accountScoreEnabled.rawValue) + } + } + } } diff --git a/fearless/Common/View/AccountScore/AccountScoreView.swift b/fearless/Common/View/AccountScore/AccountScoreView.swift index c5a9556f5..e9e29ad22 100644 --- a/fearless/Common/View/AccountScore/AccountScoreView.swift +++ b/fearless/Common/View/AccountScore/AccountScoreView.swift @@ -7,8 +7,8 @@ class AccountScoreView: UIView { private var skeletonView: SkrullableView? - let starView: CosmosView = { - let view = CosmosView() + let starView: FWCosmosView = { + let view = FWCosmosView() view.settings.totalStars = 1 view.settings.starSize = 15 view.settings.textMargin = 2 @@ -31,12 +31,15 @@ class AccountScoreView: UIView { } func bind(viewModel: AccountScoreViewModel?) { + self.viewModel = viewModel + viewModel?.setup(with: self) + if viewModel?.scoringEnabled == false { isHidden = true return } - if viewModel?.address.starts(with: "0x") != true { + if viewModel?.address?.starts(with: "0x") != true { isHidden = false bindEmptyViewModel() return @@ -44,8 +47,6 @@ class AccountScoreView: UIView { isHidden = false startLoadingIfNeeded() - self.viewModel = viewModel - viewModel?.setup(with: self) } func bind(score: Int, rate: AccountScoreRate) { @@ -98,6 +99,11 @@ class AccountScoreView: UIView { super.layoutSubviews() didUpdateSkeletonLayout() } + + override func point(inside point: CGPoint, with _: UIEvent?) -> Bool { + let oprimizedBounds = FWCosmosTouchTarget.optimize(bounds) + return oprimizedBounds.contains(point) + } } extension AccountScoreView: SkeletonLoadable { diff --git a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift index 800b420b7..53906134a 100644 --- a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift +++ b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift @@ -1,4 +1,5 @@ import UIKit +import SoraKeystore import SSFModels enum AccountScoreRate { @@ -29,21 +30,39 @@ enum AccountScoreRate { } class AccountScoreViewModel { + private let eventCenter: EventCenterProtocol private let fetcher: AccountStatisticsFetching - let address: String - let scoringEnabled: Bool + private let chain: ChainModel? + private let settings: SettingsManagerProtocol + let address: String? + var scoringEnabled: Bool weak var view: AccountScoreView? - init(fetcher: AccountStatisticsFetching, address: String, chain: ChainModel?) { + init( + fetcher: AccountStatisticsFetching, + address: String?, + chain: ChainModel?, + settings: SettingsManagerProtocol, + eventCenter: EventCenterProtocol + ) { self.fetcher = fetcher self.address = address - scoringEnabled = chain?.isNomisSupported == true || chain == nil + self.chain = chain + self.settings = settings + self.eventCenter = eventCenter + + scoringEnabled = (chain?.isNomisSupported == true || chain == nil) && settings.accountScoreEnabled == true } func setup(with view: AccountScoreView?) { + eventCenter.add(observer: self) self.view = view + guard let address else { + return + } + Task { do { let stream = try await fetcher.subscribeForStatistics(address: address, cacheOptions: .onAll) @@ -70,3 +89,13 @@ class AccountScoreViewModel { } } } + +extension AccountScoreViewModel: EventVisitorProtocol { + func processAccountScoreSettingsChanged() { + scoringEnabled = (chain?.isNomisSupported == true || chain == nil) && settings.accountScoreEnabled == true + + DispatchQueue.main.async { [weak self] in + self?.view?.bind(viewModel: self) + } + } +} diff --git a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift deleted file mode 100644 index 6cf4606eb..000000000 --- a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModelFactory.swift +++ /dev/null @@ -1,17 +0,0 @@ -// import Foundation -// -// protocol AccountScoreViewModelFactory { -// func buildViewModel(from accountStatistics: AccountStatistics) -> AccountScoreViewModel? -// } -// -// final class AccountScoreViewModelFactoryImpl: AccountScoreViewModelFactory { -// func buildViewModel(from accountStatistics: AccountStatistics) -> AccountScoreViewModel? { -// guard let score = accountStatistics.score else { -// return nil -// } -// -// let rate = AccountScoreRate(from: score) -// let intScore = ((score * 100.0) as NSDecimalNumber).intValue -// return AccountScoreViewModel(accountScoreLabelText: "\(intScore)", rate: rate) -// } -// } diff --git a/fearless/Modules/BackupWallet/BackupWalletAssembly.swift b/fearless/Modules/BackupWallet/BackupWalletAssembly.swift index 0d24dcafc..22a06bd5e 100644 --- a/fearless/Modules/BackupWallet/BackupWalletAssembly.swift +++ b/fearless/Modules/BackupWallet/BackupWalletAssembly.swift @@ -40,7 +40,7 @@ final class BackupWalletAssembly { signer: NomisRequestSigner() ) - let viewModelFactory = BackupWalletViewModelFactory(accountScoreFetcher: accountScoreFetcher) + let viewModelFactory = BackupWalletViewModelFactory(accountScoreFetcher: accountScoreFetcher, settings: SettingsManager.shared) let presenter = BackupWalletPresenter( wallet: wallet, interactor: interactor, diff --git a/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift b/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift index 40e849caf..c4f615d39 100644 --- a/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift +++ b/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift @@ -2,6 +2,7 @@ import Foundation import SoraFoundation import SSFCloudStorage import SSFModels +import SoraKeystore protocol BackupWalletViewModelFactoryProtocol { func createViewModel( @@ -35,9 +36,11 @@ enum BackupWalletOptions: Int, CaseIterable { final class BackupWalletViewModelFactory: BackupWalletViewModelFactoryProtocol { private lazy var assetBalanceFormatterFactory = AssetBalanceFormatterFactory() private let accountScoreFetcher: AccountStatisticsFetching + private let settings: SettingsManagerProtocol - init(accountScoreFetcher: AccountStatisticsFetching) { + init(accountScoreFetcher: AccountStatisticsFetching, settings: SettingsManagerProtocol) { self.accountScoreFetcher = accountScoreFetcher + self.settings = settings } func createViewModel( @@ -153,7 +156,7 @@ final class BackupWalletViewModelFactory: BackupWalletViewModelFactoryProtocol { locale: Locale ) -> WalletsManagmentCellViewModel { let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared) var fiatBalance: String = "" var dayChange: NSAttributedString? diff --git a/fearless/Modules/Contacts/ContactsAssembly.swift b/fearless/Modules/Contacts/ContactsAssembly.swift index a3db9f01c..4a4211a08 100644 --- a/fearless/Modules/Contacts/ContactsAssembly.swift +++ b/fearless/Modules/Contacts/ContactsAssembly.swift @@ -3,6 +3,7 @@ import SoraFoundation import RobinHood import SSFModels import SSFNetwork +import SoraKeystore enum ContactSource { case token(chainAsset: ChainAsset) @@ -63,7 +64,11 @@ enum ContactsAssembly { interactor: interactor, router: router, localizationManager: localizationManager, - viewModelFactory: AddressBookViewModelFactory(accountScoreFetcher: accountScoreFetcher, chain: source.chain), + viewModelFactory: AddressBookViewModelFactory( + accountScoreFetcher: accountScoreFetcher, + chain: source.chain, + settings: SettingsManager.shared + ), moduleOutput: moduleOutput, source: source, wallet: wallet diff --git a/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift b/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift index c53bf64f2..6491994ce 100644 --- a/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift +++ b/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift @@ -1,5 +1,6 @@ import Foundation import SSFModels +import SoraKeystore protocol AddressBookViewModelFactoryProtocol { func buildCellViewModels( @@ -18,13 +19,16 @@ struct ContactsTableSectionModel { final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { private let accountScoreFetcher: AccountStatisticsFetching private let chain: ChainModel + private let settings: SettingsManagerProtocol init( accountScoreFetcher: AccountStatisticsFetching, - chain: ChainModel + chain: ChainModel, + settings: SettingsManagerProtocol ) { self.accountScoreFetcher = accountScoreFetcher self.chain = chain + self.settings = settings } func buildCellViewModels( @@ -38,7 +42,9 @@ final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { let accountScoreViewModel = AccountScoreViewModel( fetcher: accountScoreFetcher, address: contactType.address, - chain: chain + chain: chain, + settings: settings, + eventCenter: EventCenter.shared ) return ContactTableCellModel( @@ -66,7 +72,9 @@ final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { let accountScoreViewModel = AccountScoreViewModel( fetcher: accountScoreFetcher, address: contact.address, - chain: chain + chain: chain, + settings: settings, + eventCenter: EventCenter.shared ) return ContactTableCellModel(contactType: .saved(contact), delegate: cellsDelegate, accountScoreViewModel: accountScoreViewModel) diff --git a/fearless/Modules/NFT/NftSend/NftSendAssembly.swift b/fearless/Modules/NFT/NftSend/NftSendAssembly.swift index f73695b99..82c7b6f4c 100644 --- a/fearless/Modules/NFT/NftSend/NftSendAssembly.swift +++ b/fearless/Modules/NFT/NftSend/NftSendAssembly.swift @@ -71,7 +71,8 @@ enum NftSendAssembly { viewModelFactory: SendViewModelFactory( iconGenerator: UniversalIconGenerator(), - accountScoreFetcher: accountStatisticsFetcher + accountScoreFetcher: accountStatisticsFetcher, + settings: SettingsManager.shared ), dataValidatingFactory: dataValidatingFactory ) diff --git a/fearless/Modules/Profile/ProfilePresenter.swift b/fearless/Modules/Profile/ProfilePresenter.swift index 9a1c62742..815577472 100644 --- a/fearless/Modules/Profile/ProfilePresenter.swift +++ b/fearless/Modules/Profile/ProfilePresenter.swift @@ -90,7 +90,7 @@ extension ProfilePresenter: ProfilePresenterProtocol { case .currency: guard let selectedWallet = selectedWallet else { return } wireframe.showSelectCurrency(from: view, with: selectedWallet) - case .biometry: + case .biometry, .accountScore: break case .walletConnect: wireframe.showWalletConnect(from: view) @@ -102,6 +102,11 @@ extension ProfilePresenter: ProfilePresenterProtocol { switch option { case .biometry: settings.biometryEnabled = isOn + case .accountScore: + settings.accountScoreEnabled = isOn + + let event = AccountScoreSettingsChanged() + eventCenter.notify(with: event) default: break } diff --git a/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift b/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift index cfc925059..dc9262475 100644 --- a/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift +++ b/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift @@ -25,6 +25,7 @@ enum ProfileOption: UInt, CaseIterable { case changePincode case biometry case about + case accountScore } final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { @@ -108,7 +109,7 @@ final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { } let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared) return WalletsManagmentCellViewModel( isSelected: false, @@ -146,6 +147,8 @@ final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { return createBiometryViewModel() case .currency: return createCurrencyViewModel(from: currency, locale: locale) + case .accountScore: + return createAccountScoreViewModel(locale: locale) } } @@ -289,6 +292,20 @@ final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { return viewModel } + private func createAccountScoreViewModel(locale: Locale) -> ProfileOptionViewModel? { + let viewModel = ProfileOptionViewModel( + title: R.string.localizable.profileAccountScoreTitle(preferredLanguages: locale.rLanguages), + icon: R.image.iconProfleAccountScore(), + accessoryTitle: nil, + accessoryImage: nil, + accessoryType: .switcher(settings.accountScoreEnabled ?? false), + option: .accountScore + ) + return viewModel + } + + // MARK: Additional + private func getDayChangeAttributedString( currency: Currency, dayChange: Decimal, diff --git a/fearless/Modules/Send/SendAssembly.swift b/fearless/Modules/Send/SendAssembly.swift index 35d820a6f..19cebe8a6 100644 --- a/fearless/Modules/Send/SendAssembly.swift +++ b/fearless/Modules/Send/SendAssembly.swift @@ -12,6 +12,7 @@ import SSFExtrinsicKit import SSFNetwork import SSFChainRegistry import SSFChainConnection +import SoraKeystore final class SendAssembly { static func configureModule( @@ -77,7 +78,8 @@ final class SendAssembly { let viewModelFactory = SendViewModelFactory( iconGenerator: UniversalIconGenerator(), - accountScoreFetcher: accountStatisticsFetcher + accountScoreFetcher: accountStatisticsFetcher, + settings: SettingsManager.shared ) let dataValidatingFactory = SendDataValidatingFactory(presentable: router) let presenter = SendPresenter( diff --git a/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift b/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift index ae605643e..1793a4d7a 100644 --- a/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift +++ b/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift @@ -1,5 +1,6 @@ import SSFUtils import SSFModels +import SoraKeystore protocol SendViewModelFactoryProtocol { func buildRecipientViewModel( @@ -14,13 +15,16 @@ protocol SendViewModelFactoryProtocol { final class SendViewModelFactory: SendViewModelFactoryProtocol { private let iconGenerator: IconGenerating private let accountScoreFetcher: AccountStatisticsFetching + private let settings: SettingsManagerProtocol init( iconGenerator: IconGenerating, - accountScoreFetcher: AccountStatisticsFetching + accountScoreFetcher: AccountStatisticsFetching, + settings: SettingsManagerProtocol ) { self.iconGenerator = iconGenerator self.accountScoreFetcher = accountScoreFetcher + self.settings = settings } func buildRecipientViewModel( @@ -46,6 +50,12 @@ final class SendViewModelFactory: SendViewModelFactoryProtocol { } func buildAccountScoreViewModel(address: String, chain: ChainModel) -> AccountScoreViewModel? { - AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: chain) + AccountScoreViewModel( + fetcher: accountScoreFetcher, + address: address, + chain: chain, + settings: settings, + eventCenter: EventCenter.shared + ) } } diff --git a/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift b/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift index 7e9ff9f47..95b2c3b79 100644 --- a/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift +++ b/fearless/Modules/WalletConnectSession/WalletConnectSessionAssembly.swift @@ -4,6 +4,7 @@ import SoraFoundation import SoraUI import RobinHood import SSFNetwork +import SoraKeystore enum WalletConnectSessionAssembly { static func configureModule( @@ -45,7 +46,8 @@ enum WalletConnectSessionAssembly { walletConnectModelFactory: walletConnectModelFactory, walletConnectPayloaFactory: walletConnectPayloaFactory, assetBalanceFormatterFactory: AssetBalanceFormatterFactory(), - accountScoreFetcher: accountScoreFetcher + accountScoreFetcher: accountScoreFetcher, + settings: SettingsManager.shared ) let presenter = WalletConnectSessionPresenter( request: request, diff --git a/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift b/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift index ebf674d7c..46883f94d 100644 --- a/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift +++ b/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift @@ -3,6 +3,7 @@ import WalletConnectSign import SoraFoundation import SSFModels import SSFUtils +import SoraKeystore protocol WalletConnectSessionViewModelFactory { func buildViewModel( @@ -20,6 +21,7 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo private let walletConnectPayloaFactory: WalletConnectPayloadFactory private let assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol private let accountScoreFetcher: AccountStatisticsFetching + private let settings: SettingsManagerProtocol init( request: Request, @@ -27,7 +29,8 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo walletConnectModelFactory: WalletConnectModelFactory, walletConnectPayloaFactory: WalletConnectPayloadFactory, assetBalanceFormatterFactory: AssetBalanceFormatterFactoryProtocol, - accountScoreFetcher: AccountStatisticsFetching + accountScoreFetcher: AccountStatisticsFetching, + settings: SettingsManagerProtocol ) { self.request = request self.session = session @@ -35,6 +38,7 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo self.walletConnectPayloaFactory = walletConnectPayloaFactory self.assetBalanceFormatterFactory = assetBalanceFormatterFactory self.accountScoreFetcher = accountScoreFetcher + self.settings = settings } func buildViewModel( @@ -106,7 +110,7 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo locale: Locale ) -> WalletsManagmentCellViewModel { let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared) guard let balance = balanceInfo?[wallet.metaId] else { return WalletsManagmentCellViewModel( diff --git a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift index 6abe19930..6e544f02b 100644 --- a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift +++ b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift @@ -1,5 +1,6 @@ import Foundation import SSFModels +import SoraKeystore protocol WalletMainContainerViewModelFactoryProtocol { func buildViewModel( @@ -12,9 +13,11 @@ protocol WalletMainContainerViewModelFactoryProtocol { final class WalletMainContainerViewModelFactory: WalletMainContainerViewModelFactoryProtocol { private let accountScoreFetcher: AccountStatisticsFetching + private let settings: SettingsManagerProtocol - init(accountScoreFetcher: AccountStatisticsFetching) { + init(accountScoreFetcher: AccountStatisticsFetching, settings: SettingsManagerProtocol) { self.accountScoreFetcher = accountScoreFetcher + self.settings = settings } func buildViewModel( @@ -53,7 +56,7 @@ final class WalletMainContainerViewModelFactory: WalletMainContainerViewModelFac } let ethAddress = selectedMetaAccount.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = ethAddress.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: ethAddress, chain: nil, settings: settings, eventCenter: EventCenter.shared) return WalletMainContainerViewModel( walletName: selectedMetaAccount.name, diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift index 218e18d49..74bda5c48 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerAssembly.swift @@ -3,6 +3,7 @@ import SoraFoundation import RobinHood import SSFUtils import SSFNetwork +import SoraKeystore final class WalletMainContainerAssembly { static func configureModule( @@ -82,7 +83,10 @@ final class WalletMainContainerAssembly { return nil } - let viewModelFactory = WalletMainContainerViewModelFactory(accountScoreFetcher: accountScoreFetcher) + let viewModelFactory = WalletMainContainerViewModelFactory( + accountScoreFetcher: accountScoreFetcher, + settings: SettingsManager.shared + ) let presenter = WalletMainContainerPresenter( balanceInfoModuleInput: balanceInfoModule.input, assetListModuleInput: assetListModule.input, diff --git a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift index 64faa7da3..0a67cbe1a 100644 --- a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift +++ b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift @@ -1,6 +1,7 @@ import Foundation import SoraFoundation import SSFModels +import SoraKeystore protocol WalletsManagmentViewModelFactoryProtocol { func buildViewModel( @@ -41,7 +42,13 @@ final class WalletsManagmentViewModelFactory: WalletsManagmentViewModelFactoryPr } let address = managedMetaAccount.info.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = address.flatMap { AccountScoreViewModel(fetcher: accountScoreFetcher, address: $0, chain: nil) } + let accountScoreViewModel = AccountScoreViewModel( + fetcher: accountScoreFetcher, + address: address, + chain: nil, + settings: SettingsManager.shared, + eventCenter: EventCenter.shared + ) guard let walletBalance = balances[key] else { return WalletsManagmentCellViewModel( diff --git a/fearless/en.lproj/Localizable.strings b/fearless/en.lproj/Localizable.strings index e8311baac..f4dc6422c 100644 --- a/fearless/en.lproj/Localizable.strings +++ b/fearless/en.lproj/Localizable.strings @@ -1207,3 +1207,4 @@ belongs to the right network"; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/id.lproj/Localizable.strings b/fearless/id.lproj/Localizable.strings index 6ce9c6b71..34dcfa39a 100644 --- a/fearless/id.lproj/Localizable.strings +++ b/fearless/id.lproj/Localizable.strings @@ -1191,3 +1191,4 @@ akan muncul di sini"; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/ja.lproj/Localizable.strings b/fearless/ja.lproj/Localizable.strings index c28fc6a24..eb96913b2 100644 --- a/fearless/ja.lproj/Localizable.strings +++ b/fearless/ja.lproj/Localizable.strings @@ -1197,3 +1197,4 @@ "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/pt-PT.lproj/Localizable.strings b/fearless/pt-PT.lproj/Localizable.strings index 33be92614..d8fe6cd6b 100644 --- a/fearless/pt-PT.lproj/Localizable.strings +++ b/fearless/pt-PT.lproj/Localizable.strings @@ -1283,3 +1283,4 @@ To find out more, contact %@"; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/ru.lproj/Localizable.strings b/fearless/ru.lproj/Localizable.strings index f7ff56506..8cc5cf515 100644 --- a/fearless/ru.lproj/Localizable.strings +++ b/fearless/ru.lproj/Localizable.strings @@ -1205,3 +1205,4 @@ Euro cash"; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/tr.lproj/Localizable.strings b/fearless/tr.lproj/Localizable.strings index e11725bfc..682e757fd 100644 --- a/fearless/tr.lproj/Localizable.strings +++ b/fearless/tr.lproj/Localizable.strings @@ -1203,3 +1203,4 @@ ait olduğundan emin olun."; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/vi.lproj/Localizable.strings b/fearless/vi.lproj/Localizable.strings index f7adc1aee..de60ae156 100644 --- a/fearless/vi.lproj/Localizable.strings +++ b/fearless/vi.lproj/Localizable.strings @@ -1205,3 +1205,4 @@ thuộc đúng mạng"; "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; diff --git a/fearless/zh-Hans.lproj/Localizable.strings b/fearless/zh-Hans.lproj/Localizable.strings index f3b101350..cf73224e5 100644 --- a/fearless/zh-Hans.lproj/Localizable.strings +++ b/fearless/zh-Hans.lproj/Localizable.strings @@ -1202,3 +1202,4 @@ "account.stats.wallet.option.title" = "Show wallet score"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; +"profile.account.score.title" = "Nomis multichain score"; From 27d377fcf1866d35a12c53b175581f69267c0404 Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Thu, 1 Aug 2024 16:22:22 +0700 Subject: [PATCH 16/18] self codereview fix --- fearless/Common/Extension/FWCosmosView.swift | 2 +- .../Services/ChainRegistry/ChainRegistry.swift | 13 ++++--------- .../PolkaswapAdjustmentAssembly.swift | 2 +- .../WalletMainContainerViewLayout.swift | 2 +- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/fearless/Common/Extension/FWCosmosView.swift b/fearless/Common/Extension/FWCosmosView.swift index 45d5f759d..42f7f5b78 100644 --- a/fearless/Common/Extension/FWCosmosView.swift +++ b/fearless/Common/Extension/FWCosmosView.swift @@ -10,7 +10,7 @@ class FWCosmosView: CosmosView { enum FWCosmosTouchTarget { static func optimize(_ bounds: CGRect) -> CGRect { - let recommendedHitSize: CGFloat = 60 + let recommendedHitSize: CGFloat = 44 var hitWidthIncrease: CGFloat = recommendedHitSize - bounds.width var hitHeightIncrease: CGFloat = recommendedHitSize - bounds.height diff --git a/fearless/Common/Services/ChainRegistry/ChainRegistry.swift b/fearless/Common/Services/ChainRegistry/ChainRegistry.swift index 531d6ead7..0478c9660 100644 --- a/fearless/Common/Services/ChainRegistry/ChainRegistry.swift +++ b/fearless/Common/Services/ChainRegistry/ChainRegistry.swift @@ -228,21 +228,16 @@ final class ChainRegistry { } chains.append(newChain) - DispatchQueue.global(qos: .background).async { - _ = try? ethereumConnectionPool.setupConnection(for: newChain) - } + _ = try? ethereumConnectionPool.setupConnection(for: newChain) } private func handleUpdatedEthereumChain(updatedChain: ChainModel) throws { guard let ethereumConnectionPool = self.ethereumConnectionPool else { return } - DispatchQueue.global(qos: .background).async { [weak self] in - guard let self else { return } - _ = try? ethereumConnectionPool.setupConnection(for: updatedChain) - self.chains = self.chains.filter { $0.chainId != updatedChain.chainId } - self.chains.append(updatedChain) - } + _ = try? ethereumConnectionPool.setupConnection(for: updatedChain) + chains = chains.filter { $0.chainId != updatedChain.chainId } + chains.append(updatedChain) } private func handleDeletedEthereumChain(chainId: ChainModel.Id) { diff --git a/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift b/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift index df32ea703..9f7a06d7a 100644 --- a/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift +++ b/fearless/Modules/PolkaswapFlow/PolkaswapAdjustment/PolkaswapAdjustmentAssembly.swift @@ -14,7 +14,7 @@ final class PolkaswapAdjustmentAssembly { let chainRegistry = ChainRegistryFacade.sharedRegistry guard - let xorChainAsset = chainRegistry.getChainUnsafe(for: Chain.soraMain.genesisHash)?.utilityChainAssets().first, + let xorChainAsset = chainRegistry.getChain(for: Chain.soraMain.genesisHash)?.utilityChainAssets().first, let connection = chainRegistry.getConnection(for: xorChainAsset.chain.chainId), let accountResponse = wallet.fetch(for: xorChainAsset.chain.accountRequest()), let runtimeService = chainRegistry.getRuntimeProvider(for: xorChainAsset.chain.chainId) diff --git a/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift b/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift index f31fef863..7ecf8701d 100644 --- a/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift +++ b/fearless/Modules/WalletMainContainer/WalletMainContainerViewLayout.swift @@ -212,7 +212,7 @@ final class WalletMainContainerViewLayout: UIView { } private func setupWalletBalanceLayout() { - addSubview(accountScoreView) + insertSubview(accountScoreView, belowSubview: navigationContainerView) accountScoreView.snp.makeConstraints { make in make.top.equalTo(navigationContainerView.snp.bottom).offset(4) make.centerX.equalTo(switchWalletButton.snp.centerX) From 229e5e4a66ecde6aad09f79a50e75d2875841bbf Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Fri, 2 Aug 2024 17:25:05 +0700 Subject: [PATCH 17/18] code review fixes --- fearless.xcodeproj/project.pbxproj | 14 +------ .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Nomis/NomisAccountStatisticsFetcher.swift | 12 +----- .../ScamService/ScamInfoFetcher.swift | 14 +++++-- .../Common/Configs/ApplicationConfigs.swift | 4 ++ .../Sources/PriceDataSource.swift | 1 - .../Foundation/NumberFormatter.swift | 8 ---- .../ChainRegistry/ChainRegistry.swift | 37 +++++++++++-------- .../View/AccountScore/AccountScoreView.swift | 13 +++---- .../AccountScore/AccountScoreViewModel.swift | 8 +++- .../AccountStatisticsAssembly.swift | 3 +- .../AccountStatisticsPresenter.swift | 9 ++++- .../AddressBookViewModelFactory.swift | 6 ++- .../Modules/NFT/NftSend/NftSendAssembly.swift | 6 ++- fearless/Modules/Send/SendAssembly.swift | 3 +- .../Send/SendDependencyContainer.swift | 1 - .../Send/ViewModel/SendViewModelFactory.swift | 3 +- ...WalletConnectSessionViewModelFactory.swift | 9 ++++- .../WalletMainContainerViewModelFactory.swift | 9 ++++- .../WalletsManagmentViewModelFactory.swift | 3 +- fearless/en.lproj/Localizable.strings | 2 + fearless/id.lproj/Localizable.strings | 2 + fearless/ja.lproj/Localizable.strings | 2 + fearless/pt-PT.lproj/Localizable.strings | 2 + fearless/pt.lproj/Localizable.strings | 6 +++ fearless/ru.lproj/Localizable.strings | 14 ++++--- fearless/tr.lproj/Localizable.strings | 12 +++--- fearless/vi.lproj/Localizable.strings | 37 ++++--------------- fearless/zh-Hans.lproj/Localizable.strings | 12 +++--- .../AccountStatisticsTests.swift | 16 -------- 30 files changed, 136 insertions(+), 136 deletions(-) delete mode 100644 fearlessTests/Modules/AccountStatistics/AccountStatisticsTests.swift diff --git a/fearless.xcodeproj/project.pbxproj b/fearless.xcodeproj/project.pbxproj index eede5373b..3f58e07b7 100644 --- a/fearless.xcodeproj/project.pbxproj +++ b/fearless.xcodeproj/project.pbxproj @@ -1314,7 +1314,6 @@ 85B1B7387F09A8405C4E688A /* WalletSendConfirmTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AF4258723E2FACBBA556D00 /* WalletSendConfirmTests.swift */; }; 872DF7DE5A001DF5B8A4E288 /* StakingBondMoreConfirmationFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BA5883C1103D3A2218D839 /* StakingBondMoreConfirmationFlow.swift */; }; 87C1FC2909A8360DDBA625E5 /* LiquidityPoolSupplyInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA528679A82B9A327853804 /* LiquidityPoolSupplyInteractor.swift */; }; - 8852522BE02B6244A00E85A1 /* AccountStatisticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26222D0DBE5CEF0BA7DCCEF7 /* AccountStatisticsTests.swift */; }; 885551F78A5926D16D5AF0CB /* ControllerAccountPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E5CB64B91B35804B3671456 /* ControllerAccountPresenter.swift */; }; 886E8CF81EF2566D98D9693E /* ExportSeedViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FA66143B25AA70B02CE461 /* ExportSeedViewFactory.swift */; }; 887CE12C7C59F5DB092E9227 /* AccountStatisticsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD63BEB84A28855006BE680 /* AccountStatisticsRouter.swift */; }; @@ -3403,7 +3402,6 @@ 25B80FDDB5C3032A0BBBD826 /* NftCollectionPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NftCollectionPresenter.swift; sourceTree = ""; }; 25D9454047EBBD8D8A0174A4 /* LiquidityPoolRemoveLiquidityRouter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LiquidityPoolRemoveLiquidityRouter.swift; sourceTree = ""; }; 25FF82C2FD912021A1F20876 /* PolkaswapAdjustmentViewLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PolkaswapAdjustmentViewLayout.swift; sourceTree = ""; }; - 26222D0DBE5CEF0BA7DCCEF7 /* AccountStatisticsTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountStatisticsTests.swift; sourceTree = ""; }; 262F98DEF54FA9592BE22B94 /* AllDoneAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AllDoneAssembly.swift; sourceTree = ""; }; 2648EEF96694A7FEC94520E8 /* WalletHistoryFilterTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletHistoryFilterTests.swift; sourceTree = ""; }; 26635BD49FBF19DB1253906E /* NetworkInfoTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NetworkInfoTests.swift; sourceTree = ""; }; @@ -7321,14 +7319,6 @@ path = ExportMnemonicConfirm; sourceTree = ""; }; - 2CFE52D4FA09CD95EA70283E /* AccountStatistics */ = { - isa = PBXGroup; - children = ( - 26222D0DBE5CEF0BA7DCCEF7 /* AccountStatisticsTests.swift */, - ); - path = AccountStatistics; - sourceTree = ""; - }; 2E04CA9A3624EA1AD62722E8 /* WalletOption */ = { isa = PBXGroup; children = ( @@ -7760,7 +7750,6 @@ 749EAA035EECE4D63C56C358 /* LiquidityPoolSupplyConfirm */, 48EAF80DCC0C537917FC5A23 /* LiquidityPoolRemoveLiquidity */, BDB80385E6818AE7707DDFF8 /* LiquidityPoolRemoveLiquidityConfirm */, - 2CFE52D4FA09CD95EA70283E /* AccountStatistics */, ); path = Modules; sourceTree = ""; @@ -19266,7 +19255,6 @@ B40863AA7377BFE5F8A30259 /* LiquidityPoolSupplyConfirmTests.swift in Sources */, 6BF307ADE63FA92389340779 /* LiquidityPoolRemoveLiquidityTests.swift in Sources */, ECA54AB8148BBA63084353FD /* LiquidityPoolRemoveLiquidityConfirmTests.swift in Sources */, - 8852522BE02B6244A00E85A1 /* AccountStatisticsTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -19817,7 +19805,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/soramitsu/shared-features-spm.git"; requirement = { - branch = "network-module-update"; + branch = "fearless-wallet"; kind = branch; }; }; diff --git a/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved b/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4d3c9fcae..13fe4284c 100644 --- a/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/fearless.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -140,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/soramitsu/shared-features-spm.git", "state" : { - "branch" : "network-module-update", - "revision" : "ef034cd4466dda1eadec92b1a8a0c4a1f4e8b71e" + "branch" : "fearless-wallet", + "revision" : "c914f1e729c4238bf6e18030e4f4c3895a94e088" } }, { diff --git a/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift index fc73c6e1f..dfbbff2c9 100644 --- a/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift +++ b/fearless/ApplicationLayer/Services/AccountStatistics/Nomis/NomisAccountStatisticsFetcher.swift @@ -23,12 +23,8 @@ extension NomisAccountStatisticsFetcher: AccountStatisticsFetching { address: String, cacheOptions: CachedNetworkRequestTrigger ) async throws -> AsyncThrowingStream, Error> { - guard let baseURL = URL(string: "https://api.nomis.cc/api/v1/multichain-score/wallet/") else { - throw NomisAccountStatisticsFetcherError.badBaseURL - } - let request = try NomisAccountStatisticsRequest( - baseURL: baseURL, + baseURL: ApplicationConfig.shared.nomisAccountScoreURL, address: address, endpoint: "score" ) @@ -38,12 +34,8 @@ extension NomisAccountStatisticsFetcher: AccountStatisticsFetching { } func fetchStatistics(address: String) async throws -> AccountStatisticsResponse? { - guard let baseURL = URL(string: "https://api.nomis.cc/api/v1/multichain-score/wallet/") else { - throw NomisAccountStatisticsFetcherError.badBaseURL - } - let request = try NomisAccountStatisticsRequest( - baseURL: baseURL, + baseURL: ApplicationConfig.shared.nomisAccountScoreURL, address: address, endpoint: "score" ) diff --git a/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift b/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift index 22d1284be..aae68cbb5 100644 --- a/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift +++ b/fearless/ApplicationLayer/Services/ScamService/ScamInfoFetcher.swift @@ -1,5 +1,6 @@ import Foundation import SSFModels +import SoraFoundation protocol ScamInfoFetching { func fetch(address: String, chain: ChainModel) async throws -> ScamInfo? @@ -12,10 +13,13 @@ final class ScamInfoFetcher: ScamInfoFetching { init( scamServiceOperationFactory: ScamServiceOperationFactoryProtocol, - accountScoreFetching: AccountStatisticsFetching + accountScoreFetching: AccountStatisticsFetching, + localizationManager: LocalizationManagerProtocol ) { self.scamServiceOperationFactory = scamServiceOperationFactory self.accountScoreFetching = accountScoreFetching + + self.localizationManager = localizationManager } func fetch(address: String, chain: ChainModel) async throws -> ScamInfo? { @@ -59,13 +63,17 @@ final class ScamInfoFetcher: ScamInfoFetching { } return ScamInfo( - name: "Nomis multi-chain score", + name: R.string.localizable.scamInfoNomisName(preferredLanguages: selectedLocale.rLanguages), address: address, type: .lowScore, - subtype: "Proceed with caution" + subtype: R.string.localizable.scamInfoNomisSubtypeText(preferredLanguages: selectedLocale.rLanguages) ) } return scamInfo } } + +extension ScamInfoFetcher: Localizable { + func applyLocalization() {} +} diff --git a/fearless/Common/Configs/ApplicationConfigs.swift b/fearless/Common/Configs/ApplicationConfigs.swift index 1b529813e..ae972bee0 100644 --- a/fearless/Common/Configs/ApplicationConfigs.swift +++ b/fearless/Common/Configs/ApplicationConfigs.swift @@ -221,6 +221,10 @@ extension ApplicationConfig: ApplicationConfigProtocol, XcmConfigProtocol { GitHubUrl.url(suffix: "appConfigs/onboarding/mobile v2.json") #endif } + + var nomisAccountScoreURL: URL { + URL(string: "https://api.nomis.cc/api/v1/multichain-score/wallet/")! + } } private enum GitHubUrl { diff --git a/fearless/Common/DataProvider/Sources/PriceDataSource.swift b/fearless/Common/DataProvider/Sources/PriceDataSource.swift index 502a49bbf..5fa2a0aee 100644 --- a/fearless/Common/DataProvider/Sources/PriceDataSource.swift +++ b/fearless/Common/DataProvider/Sources/PriceDataSource.swift @@ -183,7 +183,6 @@ final class PriceDataSource: SingleValueProviderSourceProtocol { } private func createChainlinkOperations() -> [BaseOperation] { - return [] guard currencies?.count == 1, currencies?.first?.id == Currency.defaultCurrency().id else { return [] } diff --git a/fearless/Common/Extension/Foundation/NumberFormatter.swift b/fearless/Common/Extension/Foundation/NumberFormatter.swift index 69ae67cb5..1e4cd4cd3 100644 --- a/fearless/Common/Extension/Foundation/NumberFormatter.swift +++ b/fearless/Common/Extension/Foundation/NumberFormatter.swift @@ -123,12 +123,4 @@ extension NumberFormatter { formatter.usesGroupingSeparator = usesIntGrouping return formatter } - - static var nomisHours: NumberFormatter { - let formatter = NumberFormatter.amount - formatter.roundingMode = .floor - formatter.minimumFractionDigits = 0 - formatter.maximumFractionDigits = 8 - return formatter - } } diff --git a/fearless/Common/Services/ChainRegistry/ChainRegistry.swift b/fearless/Common/Services/ChainRegistry/ChainRegistry.swift index f9dc2cbf7..61eff114e 100644 --- a/fearless/Common/Services/ChainRegistry/ChainRegistry.swift +++ b/fearless/Common/Services/ChainRegistry/ChainRegistry.swift @@ -17,7 +17,6 @@ protocol ChainRegistryProtocol: AnyObject { func getConnection(for chainId: ChainModel.Id) -> ChainConnection? func getRuntimeProvider(for chainId: ChainModel.Id) -> RuntimeProviderProtocol? func getChain(for chainId: ChainModel.Id) -> ChainModel? - func getChainUnsafe(for chainId: ChainModel.Id) -> ChainModel? func chainsSubscribe( _ target: AnyObject, runningInQueue: DispatchQueue, @@ -228,14 +227,24 @@ final class ChainRegistry { } chains.append(newChain) - _ = try? ethereumConnectionPool.setupConnection(for: newChain) + do { + _ = try ethereumConnectionPool.setupConnection(for: newChain) + } catch { + logger?.customError(error) + } } private func handleUpdatedEthereumChain(updatedChain: ChainModel) throws { guard let ethereumConnectionPool = self.ethereumConnectionPool else { return } - _ = try? ethereumConnectionPool.setupConnection(for: updatedChain) + + do { + _ = try ethereumConnectionPool.setupConnection(for: updatedChain) + } catch { + logger?.customError(error) + } + chains = chains.filter { $0.chainId != updatedChain.chainId } chains.append(updatedChain) } @@ -312,26 +321,22 @@ extension ChainRegistry: ChainRegistryProtocol { } func getEthereumConnection(for chainId: ChainModel.Id) -> Web3.Eth? { -// readLock.concurrentlyRead { - guard - let ethereumConnectionPool = self.ethereumConnectionPool, - let chain = chains.first(where: { $0.chainId == chainId }) - else { - return nil - } + readLock.concurrentlyRead { + guard + let ethereumConnectionPool = self.ethereumConnectionPool, + let chain = chains.first(where: { $0.chainId == chainId }) + else { + return nil + } - return try? ethereumConnectionPool.setupConnection(for: chain) -// } + return try? ethereumConnectionPool.setupConnection(for: chain) + } } func getChain(for chainId: ChainModel.Id) -> ChainModel? { readLock.concurrentlyRead { chains.first(where: { $0.chainId == chainId }) } } - func getChainUnsafe(for chainId: ChainModel.Id) -> ChainModel? { - chains.first(where: { $0.chainId == chainId }) - } - func getRuntimeProvider(for chainId: ChainModel.Id) -> RuntimeProviderProtocol? { runtimeProviderPool.getRuntimeProvider(for: chainId) } diff --git a/fearless/Common/View/AccountScore/AccountScoreView.swift b/fearless/Common/View/AccountScore/AccountScoreView.swift index e9e29ad22..9ef08155c 100644 --- a/fearless/Common/View/AccountScore/AccountScoreView.swift +++ b/fearless/Common/View/AccountScore/AccountScoreView.swift @@ -3,6 +3,10 @@ import SoraUI import Cosmos class AccountScoreView: UIView { + private enum Constants { + static let skeletonSize = CGSize(width: 32, height: 15) + } + private var viewModel: AccountScoreViewModel? private var skeletonView: SkrullableView? @@ -151,12 +155,7 @@ extension AccountScoreView: SkeletonLoadable { } private func setupSkeleton() { - let spaceSize = CGSize(width: 32, height: 15) - - guard spaceSize != .zero else { - self.skeletonView = Skrull(size: .zero, decorations: [], skeletons: []).build() - return - } + let spaceSize = Constants.skeletonSize let skeletonView = Skrull( size: spaceSize, @@ -181,7 +180,7 @@ extension AccountScoreView: SkeletonLoadable { SingleSkeleton.createRow( spaceSize: spaceSize, position: CGPoint(x: 0, y: 0.5), - size: CGSize(width: 32, height: 15) + size: Constants.skeletonSize ) ] } diff --git a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift index 53906134a..c5f17e12d 100644 --- a/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift +++ b/fearless/Common/ViewModel/AccountScore/AccountScoreViewModel.swift @@ -34,6 +34,8 @@ class AccountScoreViewModel { private let fetcher: AccountStatisticsFetching private let chain: ChainModel? private let settings: SettingsManagerProtocol + private let logger: LoggerProtocol? + let address: String? var scoringEnabled: Bool @@ -44,13 +46,15 @@ class AccountScoreViewModel { address: String?, chain: ChainModel?, settings: SettingsManagerProtocol, - eventCenter: EventCenterProtocol + eventCenter: EventCenterProtocol, + logger: LoggerProtocol? ) { self.fetcher = fetcher self.address = address self.chain = chain self.settings = settings self.eventCenter = eventCenter + self.logger = logger scoringEnabled = (chain?.isNomisSupported == true || chain == nil) && settings.accountScoreEnabled == true } @@ -70,7 +74,7 @@ class AccountScoreViewModel { handle(response: statistics.value) } } catch { - print("Account statistics fetching error: ", error) + logger?.debug("Account statistics fetching error: \(error)") } } } diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift b/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift index 41eae811a..4077e1223 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsAssembly.swift @@ -14,7 +14,8 @@ final class AccountStatisticsAssembly { interactor: interactor, router: router, localizationManager: localizationManager, - viewModelFactory: AccountStatisticsViewModelFactoryImpl() + viewModelFactory: AccountStatisticsViewModelFactoryImpl(), + logger: Logger.shared ) let view = AccountStatisticsViewController( diff --git a/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift b/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift index 49aa2a0e7..726a9685b 100644 --- a/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift +++ b/fearless/Modules/AccountStatistics/AccountStatisticsPresenter.swift @@ -18,6 +18,7 @@ final class AccountStatisticsPresenter { private let router: AccountStatisticsRouterInput private let interactor: AccountStatisticsInteractorInput private let viewModelFactory: AccountStatisticsViewModelFactory + private let logger: LoggerProtocol? private var accountStatistics: AccountStatistics? @@ -27,11 +28,13 @@ final class AccountStatisticsPresenter { interactor: AccountStatisticsInteractorInput, router: AccountStatisticsRouterInput, localizationManager: LocalizationManagerProtocol, - viewModelFactory: AccountStatisticsViewModelFactory + viewModelFactory: AccountStatisticsViewModelFactory, + logger: LoggerProtocol? ) { self.interactor = interactor self.router = router self.viewModelFactory = viewModelFactory + self.logger = logger self.localizationManager = localizationManager } @@ -81,7 +84,9 @@ extension AccountStatisticsPresenter: AccountStatisticsInteractorOutput { provideViewModel() } - func didReceiveAccountStatisticsError(_: Error) {} + func didReceiveAccountStatisticsError(_ error: Error) { + logger?.debug("didReceiveAccountStatisticsError \(error)") + } func didReceiveNoDataAvailableState() { view?.didReceiveError() diff --git a/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift b/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift index 6491994ce..fed4ddaf7 100644 --- a/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift +++ b/fearless/Modules/Contacts/View Models/AddressBookViewModelFactory.swift @@ -44,7 +44,8 @@ final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { address: contactType.address, chain: chain, settings: settings, - eventCenter: EventCenter.shared + eventCenter: EventCenter.shared, + logger: Logger.shared ) return ContactTableCellModel( @@ -74,7 +75,8 @@ final class AddressBookViewModelFactory: AddressBookViewModelFactoryProtocol { address: contact.address, chain: chain, settings: settings, - eventCenter: EventCenter.shared + eventCenter: EventCenter.shared, + logger: Logger.shared ) return ContactTableCellModel(contactType: .saved(contact), delegate: cellsDelegate, accountScoreViewModel: accountScoreViewModel) diff --git a/fearless/Modules/NFT/NftSend/NftSendAssembly.swift b/fearless/Modules/NFT/NftSend/NftSendAssembly.swift index 82c7b6f4c..3efa15ecc 100644 --- a/fearless/Modules/NFT/NftSend/NftSendAssembly.swift +++ b/fearless/Modules/NFT/NftSend/NftSendAssembly.swift @@ -47,7 +47,11 @@ enum NftSendAssembly { let accountInfoSubscriptionAdapter = AccountInfoSubscriptionAdapter(walletLocalSubscriptionFactory: walletLocalSubscriptionFactory, selectedMetaAccount: wallet) let accountStatisticsFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) - let scamInfoFetcher = ScamInfoFetcher(scamServiceOperationFactory: scamServiceOperationFactory, accountScoreFetching: accountStatisticsFetcher) + let scamInfoFetcher = ScamInfoFetcher( + scamServiceOperationFactory: scamServiceOperationFactory, + accountScoreFetching: accountStatisticsFetcher, + localizationManager: LocalizationManager.shared + ) let interactor = NftSendInteractor( transferService: transferService, operationManager: OperationManagerFacade.sharedManager, diff --git a/fearless/Modules/Send/SendAssembly.swift b/fearless/Modules/Send/SendAssembly.swift index 19cebe8a6..12a152c85 100644 --- a/fearless/Modules/Send/SendAssembly.swift +++ b/fearless/Modules/Send/SendAssembly.swift @@ -59,7 +59,8 @@ final class SendAssembly { let accountStatisticsFetcher = NomisAccountStatisticsFetcher(networkWorker: NetworkWorkerImpl(), signer: NomisRequestSigner()) let scamInfoFetcher = ScamInfoFetcher( scamServiceOperationFactory: scamServiceOperationFactory, - accountScoreFetching: accountStatisticsFetcher + accountScoreFetching: accountStatisticsFetcher, + localizationManager: LocalizationManager.shared ) let interactor = SendInteractor( accountInfoSubscriptionAdapter: AccountInfoSubscriptionAdapter( diff --git a/fearless/Modules/Send/SendDependencyContainer.swift b/fearless/Modules/Send/SendDependencyContainer.swift index b5bfe48f5..ee8514336 100644 --- a/fearless/Modules/Send/SendDependencyContainer.swift +++ b/fearless/Modules/Send/SendDependencyContainer.swift @@ -80,7 +80,6 @@ final class SendDepencyContainer { storageRequestPerformer: storageRequestPerformer ) -// cachedDependencies[chainAsset.uniqueKey(accountId: accountResponse.accountId)] = dependencies currentDependecies = dependencies return dependencies diff --git a/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift b/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift index 1793a4d7a..147fdf635 100644 --- a/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift +++ b/fearless/Modules/Send/ViewModel/SendViewModelFactory.swift @@ -55,7 +55,8 @@ final class SendViewModelFactory: SendViewModelFactoryProtocol { address: address, chain: chain, settings: settings, - eventCenter: EventCenter.shared + eventCenter: EventCenter.shared, + logger: Logger.shared ) } } diff --git a/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift b/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift index 46883f94d..20095f160 100644 --- a/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift +++ b/fearless/Modules/WalletConnectSession/WalletConnectSessionViewModelFactory.swift @@ -110,7 +110,14 @@ final class WalletConnectSessionViewModelFactoryImpl: WalletConnectSessionViewMo locale: Locale ) -> WalletsManagmentCellViewModel { let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared) + let accountScoreViewModel = AccountScoreViewModel( + fetcher: accountScoreFetcher, + address: address, + chain: nil, + settings: settings, + eventCenter: EventCenter.shared, + logger: Logger.shared + ) guard let balance = balanceInfo?[wallet.metaId] else { return WalletsManagmentCellViewModel( diff --git a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift index 6e544f02b..c5d744400 100644 --- a/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift +++ b/fearless/Modules/WalletMainContainer/ViewModel/WalletMainContainerViewModelFactory.swift @@ -56,7 +56,14 @@ final class WalletMainContainerViewModelFactory: WalletMainContainerViewModelFac } let ethAddress = selectedMetaAccount.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: ethAddress, chain: nil, settings: settings, eventCenter: EventCenter.shared) + let accountScoreViewModel = AccountScoreViewModel( + fetcher: accountScoreFetcher, + address: ethAddress, + chain: nil, + settings: settings, + eventCenter: EventCenter.shared, + logger: Logger.shared + ) return WalletMainContainerViewModel( walletName: selectedMetaAccount.name, diff --git a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift index 0a67cbe1a..86f4472e2 100644 --- a/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift +++ b/fearless/Modules/WalletsManagment/ViewModel/WalletsManagmentViewModelFactory.swift @@ -47,7 +47,8 @@ final class WalletsManagmentViewModelFactory: WalletsManagmentViewModelFactoryPr address: address, chain: nil, settings: SettingsManager.shared, - eventCenter: EventCenter.shared + eventCenter: EventCenter.shared, + logger: Logger.shared ) guard let walletBalance = balances[key] else { diff --git a/fearless/en.lproj/Localizable.strings b/fearless/en.lproj/Localizable.strings index f4dc6422c..d8aadf048 100644 --- a/fearless/en.lproj/Localizable.strings +++ b/fearless/en.lproj/Localizable.strings @@ -1208,3 +1208,5 @@ belongs to the right network"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; "profile.account.score.title" = "Nomis multichain score"; +"scam.info.nomis.name" = "Nomis multi-chain score"; +"scam.info.nomis.subtype.text" = "Proceed with caution"; diff --git a/fearless/id.lproj/Localizable.strings b/fearless/id.lproj/Localizable.strings index 34dcfa39a..faf06dc9b 100644 --- a/fearless/id.lproj/Localizable.strings +++ b/fearless/id.lproj/Localizable.strings @@ -1192,3 +1192,5 @@ akan muncul di sini"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; "profile.account.score.title" = "Nomis multichain score"; +"scam.info.nomis.name" = "Nomis multi-chain score"; +"scam.info.nomis.subtype.text" = "Proceed with caution"; diff --git a/fearless/ja.lproj/Localizable.strings b/fearless/ja.lproj/Localizable.strings index eb96913b2..964bb6e5b 100644 --- a/fearless/ja.lproj/Localizable.strings +++ b/fearless/ja.lproj/Localizable.strings @@ -1198,3 +1198,5 @@ "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; "profile.account.score.title" = "Nomis multichain score"; +"scam.info.nomis.name" = "Nomis multi-chain score"; +"scam.info.nomis.subtype.text" = "Proceed with caution"; diff --git a/fearless/pt-PT.lproj/Localizable.strings b/fearless/pt-PT.lproj/Localizable.strings index d8fe6cd6b..3c30eb766 100644 --- a/fearless/pt-PT.lproj/Localizable.strings +++ b/fearless/pt-PT.lproj/Localizable.strings @@ -1284,3 +1284,5 @@ To find out more, contact %@"; "scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; "profile.account.score.title" = "Nomis multichain score"; +"scam.info.nomis.name" = "Nomis multi-chain score"; +"scam.info.nomis.subtype.text" = "Proceed with caution"; diff --git a/fearless/pt.lproj/Localizable.strings b/fearless/pt.lproj/Localizable.strings index 18d5ca956..41d5bd73f 100644 --- a/fearless/pt.lproj/Localizable.strings +++ b/fearless/pt.lproj/Localizable.strings @@ -66,6 +66,7 @@ "account.option" = "Opção de conta"; "account.stats.avg.transaction.time.title" = "Avg. transaction time"; "account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; "account.stats.hold.tokens.usd.title" = "Hold tokens USD"; "account.stats.max.transaction.time.title" = "Max transaction time"; "account.stats.min.transactions.time.title" = "Min transaction time"; @@ -75,6 +76,7 @@ "account.stats.total.transactions.title" = "Total transactions"; "account.stats.updated.title" = "Updated"; "account.stats.wallet.age.title" = "Wallet age"; +"account.stats.wallet.option.title" = "Show wallet score"; "account.template" = "%s conta"; "account.unique.secret" = "Contas com segredos exclusivos"; "accounts.add.account" = "Adicionar conta"; @@ -710,6 +712,7 @@ Estes documentos são cruciais para uma experiência segura e positiva do utiliz "pools.limit.has.reached.error.message" = "O limite de pools nesta rede foi atingido"; "pools.limit.has.reached.error.title" = "Você não pode criar mais pools"; "profile.about.title" = "Sobre"; +"profile.account.score.title" = "Nomis multichain score"; "profile.accounts.title" = "Contas"; "profile.language.title" = "Idioma"; "profile.logout.description" = "Esta ação resultará na eliminação de todas as contas deste dispositivo. Certifique-se de que fez o backup da sua frase-chave antes de prosseguir."; @@ -737,8 +740,11 @@ Estes documentos são cruciais para uma experiência segura e positiva do utiliz "scam.additional.stub" = "Adicional:"; "scam.description.donation.stub" = "Este endereço foi sinalizado como suspeito. Recomendamos fortemente que você não envie %s para esta conta."; "scam.description.exchange.stub" = "Este endereço está marcado como uma corretora, tome cuidado pois os endereços de depósito e saque podem ser diferentes."; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "scam.description.sanctions.stub" = "Este endereço foi sinalizado devido a uma entidade relacionada a um país sob sanções. Recomendamos fortemente que você não envie %s para esta conta."; "scam.description.scam.stub" = "Este endereço foi sinalizado devido a evidências de fraude. Recomendamos fortemente que você não envie %s para esta conta."; +"scam.info.nomis.name" = "Nomis multi-chain score"; +"scam.info.nomis.subtype.text" = "Proceed with caution"; "scam.name.stub" = "Nome:"; "scam.reason.stub" = "Motivo:"; "scam.warning.alert.subtitle" = "Recomendamos fortemente que você não envie %s para esta conta."; diff --git a/fearless/ru.lproj/Localizable.strings b/fearless/ru.lproj/Localizable.strings index 8cc5cf515..f1fe5dbf9 100644 --- a/fearless/ru.lproj/Localizable.strings +++ b/fearless/ru.lproj/Localizable.strings @@ -1,7 +1,7 @@ "NSCameraUsageDescription" = "Камера используется для сканирования QR кода."; "NSFaceIDUsageDescription" = "Fearless использует Face ID, чтобы ограничить несанкционированных пользователей доступа к приложению.Face ID используется для авторизации в приложении"; "NSPhotoLibraryAddUsageDescription" = "Вы можете сохранить запрос на перевод в виде QR кода"; -"NSPhotoLibraryUsageDescription" = "Загрузить изображения из библиотеки"; +"NSPhotoLibraryUsageDescription" = "Для выбора существующих изображений QR-кодов необходим доступ к библиотеке"; "about.announcement" = "Следить за новостями в Telegram"; "about.ask.for.support" = "Обратитесь за поддержкой"; "about.contact.email" = "Контактный адрес электронной почты"; @@ -66,6 +66,7 @@ "account.option" = "Account option"; "account.stats.avg.transaction.time.title" = "Avg. transaction time"; "account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; "account.stats.hold.tokens.usd.title" = "Hold tokens USD"; "account.stats.max.transaction.time.title" = "Max transaction time"; "account.stats.min.transactions.time.title" = "Min transaction time"; @@ -75,6 +76,7 @@ "account.stats.total.transactions.title" = "Total transactions"; "account.stats.updated.title" = "Updated"; "account.stats.wallet.age.title" = "Wallet age"; +"account.stats.wallet.option.title" = "Show wallet score"; "account.template" = "%s аккаунт"; "account.unique.secret" = "Аккаунты с измененным ключом"; "accounts.add.account" = "Добавить аккаунт"; @@ -702,6 +704,7 @@ Euro cash"; "pools.limit.has.reached.error.message" = "Достигнут лимит пулов в этой сети"; "pools.limit.has.reached.error.title" = "Вы не можете создать больше пулов"; "profile.about.title" = "О приложении"; +"profile.account.score.title" = "Nomis multichain score"; "profile.accounts.title" = "Аккаунты"; "profile.language.title" = "Язык"; "profile.logout.description" = "Это действие приведет к удалению всех учетных записей с этого устройства. Прежде чем продолжить, убедитесь, что вы сделали резервную копию своей парольной фразы."; @@ -729,8 +732,11 @@ Euro cash"; "scam.additional.stub" = "Дополнительно:"; "scam.description.donation.stub" = "Этот адрес подозрителен. Мы настоятельно рекомендуем вам не отправлять %s на этот аккаунт."; "scam.description.exchange.stub" = "This address is marked as an exchange, be careful as the deposit and withdrawal addresses may different."; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "scam.description.sanctions.stub" = "This address has been flagged due to an entity related to a country under sanctions. We strongly recommend that you don't send %s to this account."; "scam.description.scam.stub" = "Этот адрес был помечен в связи с доказательствами мошенничества. Мы настоятельно рекомендуем вам не отправлять %s на этот аккаунт."; +"scam.info.nomis.name" = "Nomis multi-chain score"; +"scam.info.nomis.subtype.text" = "Proceed with caution"; "scam.name.stub" = "Имя:"; "scam.reason.stub" = "Причина:"; "scam.warning.alert.subtitle" = "Мы настоятельно рекомендуем не отправлять %s на это аккаунт."; @@ -1201,8 +1207,4 @@ Euro cash"; "your.validators.change.validators.title" = "Изменить валидаторов"; "your.validators.stop.nominating.title" = "Stop nominating"; "your.validators.validator.total.stake" = "Всего в стейкинге: %@"; -"сurrencies.stub.text" = "Токены"; -"account.stats.wallet.option.title" = "Show wallet score"; -"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; -"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; -"profile.account.score.title" = "Nomis multichain score"; +"сurrencies.stub.text" = "Токены"; \ No newline at end of file diff --git a/fearless/tr.lproj/Localizable.strings b/fearless/tr.lproj/Localizable.strings index 682e757fd..9b74a7447 100644 --- a/fearless/tr.lproj/Localizable.strings +++ b/fearless/tr.lproj/Localizable.strings @@ -66,6 +66,7 @@ "account.option" = "Hesap seçeneği"; "account.stats.avg.transaction.time.title" = "Avg. transaction time"; "account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; "account.stats.hold.tokens.usd.title" = "Hold tokens USD"; "account.stats.max.transaction.time.title" = "Max transaction time"; "account.stats.min.transactions.time.title" = "Min transaction time"; @@ -75,6 +76,7 @@ "account.stats.total.transactions.title" = "Total transactions"; "account.stats.updated.title" = "Updated"; "account.stats.wallet.age.title" = "Wallet age"; +"account.stats.wallet.option.title" = "Show wallet score"; "account.template" = "%s hesabı"; "account.unique.secret" = "Eşsiz gizli bilgili hesaplar"; "accounts.add.account" = "Hesap ekle"; @@ -703,6 +705,7 @@ burada görmek için NFT'leri satın alın veya bastırın"; "pools.limit.has.reached.error.message" = "Bu ağdaki havuz sınırına ulaşıldı"; "pools.limit.has.reached.error.title" = "Daha fazla havuz oluşturamazsınız"; "profile.about.title" = "Hakkında"; +"profile.account.score.title" = "Nomis multichain score"; "profile.accounts.title" = "Hesaplar"; "profile.language.title" = "Dil"; "profile.logout.description" = "Bu işlem ,cihazdaki tüm hesapların silinmesine neden olacak. Devam etmeden önce anahtar kelimelerinizi yedeklediğinizden emin olun."; @@ -730,8 +733,11 @@ burada görmek için NFT'leri satın alın veya bastırın"; "scam.additional.stub" = "Ek olarak:"; "scam.description.donation.stub" = "Bu adres şüpheli olarak işaretlendi. göndermemenizi şiddetle tavsiye ederiz.%sbu hesaba."; "scam.description.exchange.stub" = "Bu adres bir borsa olarak işaretlenmiştir, para yatırma ve çekme adresleri farklı olabileceğinden dikkatli olun."; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "scam.description.sanctions.stub" = "Bu adres, yaptırım altındaki bir ülkeyle bağlantılı bir kuruluş nedeniyle işaretlendi. göndermemenizi şiddetle tavsiye ederiz.%sbu hesaba."; "scam.description.scam.stub" = "Bu adres dolandırıcılık kanıtı nedeniyle işaretlendi. göndermemenizi şiddetle tavsiye ederiz.%sbu hesaba."; +"scam.info.nomis.name" = "Nomis multi-chain score"; +"scam.info.nomis.subtype.text" = "Proceed with caution"; "scam.name.stub" = "İsim:"; "scam.reason.stub" = "Sebep:"; "scam.warning.alert.subtitle" = "göndermemenizi şiddetle tavsiye ederiz.%sbu hesaba."; @@ -1199,8 +1205,4 @@ ait olduğundan emin olun."; "your.validators.change.validators.title" = "Doğrulayıcıları değiştir"; "your.validators.stop.nominating.title" = "Aday göstermeyi bırak"; "your.validators.validator.total.stake" = "Toplam yatırılan miktar:%@"; -"сurrencies.stub.text" = "Para birimleri"; -"account.stats.wallet.option.title" = "Show wallet score"; -"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; -"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; -"profile.account.score.title" = "Nomis multichain score"; +"сurrencies.stub.text" = "Para birimleri"; \ No newline at end of file diff --git a/fearless/vi.lproj/Localizable.strings b/fearless/vi.lproj/Localizable.strings index 2619271e2..6477046c6 100644 --- a/fearless/vi.lproj/Localizable.strings +++ b/fearless/vi.lproj/Localizable.strings @@ -66,6 +66,7 @@ "account.option" = "Tùy chọn tài khoản"; "account.stats.avg.transaction.time.title" = "Avg. transaction time"; "account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; "account.stats.hold.tokens.usd.title" = "Hold tokens USD"; "account.stats.max.transaction.time.title" = "Max transaction time"; "account.stats.min.transactions.time.title" = "Min transaction time"; @@ -75,6 +76,7 @@ "account.stats.total.transactions.title" = "Total transactions"; "account.stats.updated.title" = "Updated"; "account.stats.wallet.age.title" = "Wallet age"; +"account.stats.wallet.option.title" = "Show wallet score"; "account.template" = "tài khoản %s"; "account.unique.secret" = "Tài khoản có khóa bí mật duy nhất"; "accounts.add.account" = "Thêm tài khoản"; @@ -481,7 +483,6 @@ Seed: %@"; "label.testnet" = "Testnet"; "language.title" = "Ngôn ngữ"; "learn.more.about.crowdloans" = "Tìm hiểu thêm về Crowdloans"; -<<<<<<< HEAD "lp.apy.alert.text" = "Phần thưởng canh tác để cung cấp thanh khoản"; "lp.apy.alert.title" = "APY tiền thưởng chiến lược"; "lp.apy.title" = "APY tiền thưởng chiến lược"; @@ -504,30 +505,6 @@ Seed: %@"; "lp.supply.liquidity.screen.title" = "Nguồnn cung thanh khoản"; "lp.token.pooled.text" = "%@ của bạn đã đóng góp"; "lp.user.pools.title" = "Pool người dùng"; -======= -"lp.apy.alert.text" = "Farming reward for liquidity provision"; -"lp.apy.alert.title" = "Strategic Bonus APY"; -"lp.apy.title" = "Strategic Bonus APY"; -"lp.available.pools.title" = "Available pools"; -"lp.banner.action.details.title" = "Show details"; -"lp.banner.text" = "Invest your funds in Liquidity\npools and receive rewards"; -"lp.confirm.liquidity.screen.title" = "Confirm Liquidity"; -"lp.confirm.liquidity.warning.text" = "Output is estimated. If the price changes more than 0.5% your transaction will revert."; -"lp.network.fee.alert.text" = "Network fee is used to ensure SORA system’s growth and stable performance."; -"lp.network.fee.alert.title" = "Network fee"; -"lp.pool.details.title" = "Pool details"; -"lp.pool.remove.warning.text" = "Removing pool tokens converts your position back into underlying tokens at the current rate, proportional to your share of the pool. Accrued fees are included in the amounts you receive."; -"lp.pool.remove.warning.title" = "NOTE"; -"lp.remove.button.title" = "Remove Liquidity"; -"lp.remove.liquidity.screen.title" = "Remove Liquidity"; -"lp.reward.token.text" = "Earn %@"; -"lp.reward.token.title" = "Rewards Payout In"; -"lp.slippage.title" = "Slippage"; -"lp.supply.button.title" = "Supply Liquidity"; -"lp.supply.liquidity.screen.title" = "Supply Liquidity"; -"lp.token.pooled.text" = "Your %@ Pooled"; -"lp.user.pools.title" = "User pools"; ->>>>>>> 90f6e29d154045caf46fefca3c8403f8d95482ea "manage.assets.account.missing.text" = "Thêm một tài khoản..."; "manage.assets.search.hint" = "Tìm kiếm theo tài sản"; "members.common" = "Các thành viên"; @@ -730,6 +707,7 @@ Xin lưu ý rằng việc ký giao dịch này sẽ phải chịu phí giao dị "pools.limit.has.reached.error.message" = "Đã đạt đến giới hạn pool trong mạng này"; "pools.limit.has.reached.error.title" = "Bạn không thể tạo thêm pool"; "profile.about.title" = "Giới thiệu"; +"profile.account.score.title" = "Nomis multichain score"; "profile.accounts.title" = "Tài khoản"; "profile.language.title" = "Ngôn ngữ"; "profile.logout.description" = "Hành động này sẽ dẫn đến việc xóa tất cả các tài khoản khỏi thiết bị này. Hãy đảm bảo rằng bạn đã sao lưu cụm mật khẩu của mình trước khi tiếp tục."; @@ -757,8 +735,11 @@ Xin lưu ý rằng việc ký giao dịch này sẽ phải chịu phí giao dị "scam.additional.stub" = "Bổ sung:"; "scam.description.donation.stub" = "Địa chỉ này đã được gắn cờ là đáng ngờ. Chúng tôi thực sự khuyên bạn không nên gửi %s tới tài khoản này."; "scam.description.exchange.stub" = "Địa chỉ này được đánh dấu là một sàn giao dịch, hãy cẩn thận, địa chỉ gửi tiền và địa chỉ rút tiền có thể khác nhau."; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "scam.description.sanctions.stub" = "Địa chỉ này đã bị gắn cờ do có liên quan đến một quốc gia đang bị trừng phạt. Chúng tôi thực sự khuyên bạn không nên gửi %s vào tài khoản này."; "scam.description.scam.stub" = "Địa chỉ này đã bị gắn cờ do có bằng chứng lừa đảo. Chúng tôi thực sự khuyên bạn không nên gửi {asset} tới tài khoản này."; +"scam.info.nomis.name" = "Nomis multi-chain score"; +"scam.info.nomis.subtype.text" = "Proceed with caution"; "scam.name.stub" = "Tên:"; "scam.reason.stub" = "Lý do:"; "scam.warning.alert.subtitle" = "Chúng tôi thực sự khuyên bạn không nên gửi %s vào tài khoản này."; @@ -1226,8 +1207,4 @@ thuộc đúng mạng"; "your.validators.change.validators.title" = "Thay đổi validator"; "your.validators.stop.nominating.title" = "Dừng đề cử"; "your.validators.validator.total.stake" = "Tổng đã stake: %@"; -"сurrencies.stub.text" = "Tiền tệ"; -"account.stats.wallet.option.title" = "Show wallet score"; -"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; -"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; -"profile.account.score.title" = "Nomis multichain score"; +"сurrencies.stub.text" = "Tiền tệ"; \ No newline at end of file diff --git a/fearless/zh-Hans.lproj/Localizable.strings b/fearless/zh-Hans.lproj/Localizable.strings index cf73224e5..4459f99d2 100644 --- a/fearless/zh-Hans.lproj/Localizable.strings +++ b/fearless/zh-Hans.lproj/Localizable.strings @@ -66,6 +66,7 @@ "account.option" = "账户选项"; "account.stats.avg.transaction.time.title" = "Avg. transaction time"; "account.stats.description.text" = "Your Multichain Score is based on your onchain journey through 3 ecosystems: Ethereum, Polygon and Binance Smart Chain"; +"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; "account.stats.hold.tokens.usd.title" = "Hold tokens USD"; "account.stats.max.transaction.time.title" = "Max transaction time"; "account.stats.min.transactions.time.title" = "Min transaction time"; @@ -75,6 +76,7 @@ "account.stats.total.transactions.title" = "Total transactions"; "account.stats.updated.title" = "Updated"; "account.stats.wallet.age.title" = "Wallet age"; +"account.stats.wallet.option.title" = "Show wallet score"; "account.template" = "%s账户"; "account.unique.secret" = "拥有独特密钥的账户"; "accounts.add.account" = "添加一个账户"; @@ -705,6 +707,7 @@ "pools.limit.has.reached.error.message" = "这个网络的池子数量已经达到了上限"; "pools.limit.has.reached.error.title" = "你不能创建更多的池子"; "profile.about.title" = "关于"; +"profile.account.score.title" = "Nomis multichain score"; "profile.accounts.title" = "账户"; "profile.language.title" = "语言"; "profile.logout.description" = "此操作将导致删除该设备上的所有账户。在继续之前,请确保已备份您的密码短语。"; @@ -732,8 +735,11 @@ "scam.additional.stub" = "附加:"; "scam.description.donation.stub" = "此地址已被标记为可疑。我们强烈建议您不要向该账户发送%s。"; "scam.description.exchange.stub" = "这个地址被标记为交易所,请注意存款和提款地址可能不同。"; +"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; "scam.description.sanctions.stub" = "由于与受制裁国家相关的实体,此地址已被标记。我们强烈建议您不要向此账户发送%s。"; "scam.description.scam.stub" = "此地址已被标记存在诈骗嫌疑。我们强烈建议您不要将{asset}发送到此账户。"; +"scam.info.nomis.name" = "Nomis multi-chain score"; +"scam.info.nomis.subtype.text" = "Proceed with caution"; "scam.name.stub" = "姓名:"; "scam.reason.stub" = "原因:"; "scam.warning.alert.subtitle" = "我们强烈建议您不要向此账户发送%s。"; @@ -1198,8 +1204,4 @@ "your.validators.change.validators.title" = "更改验证人"; "your.validators.stop.nominating.title" = "停止提名"; "your.validators.validator.total.stake" = "总质押:%@"; -"сurrencies.stub.text" = "货币"; -"account.stats.wallet.option.title" = "Show wallet score"; -"scam.description.lowscore.text" = "The address you are about to transfer to has low onchain activity, which may indicate a potential scammer or sybil attacker"; -"account.stats.error.message" = "Unable to retrieve account score information. Please try again later."; -"profile.account.score.title" = "Nomis multichain score"; +"сurrencies.stub.text" = "货币"; \ No newline at end of file diff --git a/fearlessTests/Modules/AccountStatistics/AccountStatisticsTests.swift b/fearlessTests/Modules/AccountStatistics/AccountStatisticsTests.swift deleted file mode 100644 index 67073583f..000000000 --- a/fearlessTests/Modules/AccountStatistics/AccountStatisticsTests.swift +++ /dev/null @@ -1,16 +0,0 @@ -import XCTest - -class AccountStatisticsTests: XCTestCase { - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() { - XCTFail("Did you forget to add tests?") - } -} From 8b538e2a4decbe9a53a10f1839c4175b87b9bb0d Mon Sep 17 00:00:00 2001 From: Alex Lebedko Date: Fri, 2 Aug 2024 17:36:21 +0700 Subject: [PATCH 18/18] build fix --- .../Modules/BackupWallet/BackupWalletViewModelFactory.swift | 2 +- .../Modules/Profile/ViewModel/ProfileViewModelFactory.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift b/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift index c4f615d39..305507a95 100644 --- a/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift +++ b/fearless/Modules/BackupWallet/BackupWalletViewModelFactory.swift @@ -156,7 +156,7 @@ final class BackupWalletViewModelFactory: BackupWalletViewModelFactoryProtocol { locale: Locale ) -> WalletsManagmentCellViewModel { let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared) + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared, logger: Logger.shared) var fiatBalance: String = "" var dayChange: NSAttributedString? diff --git a/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift b/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift index dc9262475..fc646ff0e 100644 --- a/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift +++ b/fearless/Modules/Profile/ViewModel/ProfileViewModelFactory.swift @@ -109,7 +109,7 @@ final class ProfileViewModelFactory: ProfileViewModelFactoryProtocol { } let address = wallet.ethereumAddress?.toHex(includePrefix: true) - let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared) + let accountScoreViewModel = AccountScoreViewModel(fetcher: accountScoreFetcher, address: address, chain: nil, settings: settings, eventCenter: EventCenter.shared, logger: Logger.shared) return WalletsManagmentCellViewModel( isSelected: false,