diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 95c0a4cc7e..bdc14574c7 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -125,7 +125,6 @@ 48E7D7072820F66F286A0B9D /* StakingMainViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55CC5A63AC07644409E997C1 /* StakingMainViewController.xib */; }; 4A520B7081BE2D7604B69354 /* AccountImportWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F45A5C6145F863760F4409 /* AccountImportWireframe.swift */; }; 4A83E8B5CC4B87B95CB4E10B /* DAppTxDetailsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D0AFE0AC8152E50CEF7E87 /* DAppTxDetailsTests.swift */; }; - 4C00E26598A13EB86B702A0E /* DAppSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48D6D4A250A171A3A22E3EDF /* DAppSearchInteractor.swift */; }; 4C7AC4E214171478AC98A898 /* StakingRewardDestConfirmTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9403C5F9C88A4690C62A204B /* StakingRewardDestConfirmTests.swift */; }; 4E5CD7B8821FA5298EA1598E /* CrowdloanContributionSetupWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3574BADE9CF77599048C7010 /* CrowdloanContributionSetupWireframe.swift */; }; 4F932A5E6F990DC5EDF56215 /* AccountManagementViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2A0AC679F039526102165678 /* AccountManagementViewController.xib */; }; @@ -361,6 +360,9 @@ 84293E62261BBFD7008E5EDD /* TransactionDetailsViewModelFactory+Transfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84293E61261BBFD7008E5EDD /* TransactionDetailsViewModelFactory+Transfer.swift */; }; 84293E67261BC50F008E5EDD /* TransactionDetailsViewModelFactory+RewardsAndSlashes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84293E66261BC50F008E5EDD /* TransactionDetailsViewModelFactory+RewardsAndSlashes.swift */; }; 84293E6C261BCCF1008E5EDD /* TransactionDetailsViewModelFactory+Extrinsic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84293E6B261BCCF1008E5EDD /* TransactionDetailsViewModelFactory+Extrinsic.swift */; }; + 842BA36D27B64F1000D31EEF /* DAppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842BA36A27B64F1000D31EEF /* DAppViewModel.swift */; }; + 842BA36E27B64F1000D31EEF /* DAppListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842BA36B27B64F1000D31EEF /* DAppListState.swift */; }; + 842BA36F27B64F1000D31EEF /* DAppListViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842BA36C27B64F1000D31EEF /* DAppListViewModelFactory.swift */; }; 842BDB1E278C1CB100AB4B5A /* DAppBrowserStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842BDB1D278C1CB100AB4B5A /* DAppBrowserStateMachine.swift */; }; 842BDB21278C222100AB4B5A /* DAppBrowserBaseState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842BDB20278C222100AB4B5A /* DAppBrowserBaseState.swift */; }; 842BDB23278C229D00AB4B5A /* DAppBrowserStateDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842BDB22278C229D00AB4B5A /* DAppBrowserStateDataSource.swift */; }; @@ -690,9 +692,6 @@ 846E5013277999FC0049B659 /* DAppAuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E5012277999FC0049B659 /* DAppAuthViewModel.swift */; }; 846E501527799B3E0049B659 /* DAppAuthViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E501427799B3E0049B659 /* DAppAuthViewModelFactory.swift */; }; 846E5018277AD3D50049B659 /* DAppList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E5017277AD3D50049B659 /* DAppList.swift */; }; - 846E501B277ADB6B0049B659 /* DAppListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E501A277ADB6B0049B659 /* DAppListState.swift */; }; - 846E501D277ADB8C0049B659 /* DAppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E501C277ADB8C0049B659 /* DAppViewModel.swift */; }; - 846E501F277ADD280049B659 /* DAppListViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E501E277ADD280049B659 /* DAppListViewModelFactory.swift */; }; 8470D6CD253E3170009E9A5D /* AccountInfoSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8470D6CC253E3170009E9A5D /* AccountInfoSubscription.swift */; }; 8470D6D0253E321C009E9A5D /* StorageSubscriptionProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8470D6CF253E321C009E9A5D /* StorageSubscriptionProtocols.swift */; }; 8470D6D2253E3382009E9A5D /* StorageSubscriptionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8470D6D1253E3382009E9A5D /* StorageSubscriptionContainer.swift */; }; @@ -1098,6 +1097,7 @@ 84CA68DF26BEAA0F003B9453 /* ChainModelMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CA68DE26BEAA0F003B9453 /* ChainModelMapper.swift */; }; 84CA68E126BEAC7C003B9453 /* SpecVersionSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CA68E026BEAC7C003B9453 /* SpecVersionSubscriptionFactory.swift */; }; 84CB2250270360AC0041C8C1 /* StakingLocalSubscriptionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CB224F270360AC0041C8C1 /* StakingLocalSubscriptionFactory.swift */; }; + 84CC128627B8336900C8E6AB /* AsyncClosureOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC128527B8336900C8E6AB /* AsyncClosureOperation.swift */; }; 84CCBFBC2509709500180F4F /* UIBarButtonItem+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CCBFBB2509709500180F4F /* UIBarButtonItem+Style.swift */; }; 84CD3570252620FB0081BC0B /* CryptoType+Extrinsic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CD356F252620FB0081BC0B /* CryptoType+Extrinsic.swift */; }; 84CD82AE263C1452001A6F01 /* SubstrateProviderSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CD82AD263C1452001A6F01 /* SubstrateProviderSubscriber.swift */; }; @@ -1263,6 +1263,9 @@ 84EC2D18276B9DBC009B0BE1 /* PolkadotExtensionAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EC2D17276B9DBC009B0BE1 /* PolkadotExtensionAccount.swift */; }; 84EC2D1A276C6600009B0BE1 /* PolkadotExtensionExtrinsic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EC2D19276C6600009B0BE1 /* PolkadotExtensionExtrinsic.swift */; }; 84EC2D1D276C684D009B0BE1 /* PolkadotExtensionSignerResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EC2D1C276C684D009B0BE1 /* PolkadotExtensionSignerResult.swift */; }; + 84EE780227C4C2A40027357F /* DAppListFeaturedHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EE780127C4C2A40027357F /* DAppListFeaturedHeaderView.swift */; }; + 84EE780427C4CF9E0027357F /* DAppListItemsLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EE780327C4CF9E0027357F /* DAppListItemsLoadingView.swift */; }; + 84EE780627C4DF130027357F /* DAppSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EE780527C4DF130027357F /* DAppSearchInteractor.swift */; }; 84F0316F27302888003BDCF8 /* CoingeckoPriceListSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F0316E27302888003BDCF8 /* CoingeckoPriceListSource.swift */; }; 84F13F0826F13898006725FF /* StakingAssetSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F13F0726F13898006725FF /* StakingAssetSettings.swift */; }; 84F13F0A26F14122006725FF /* ChainAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F13F0926F14122006725FF /* ChainAsset.swift */; }; @@ -2016,7 +2019,6 @@ 48B426F4F2C616164A00FAD5 /* ModifyConnectionViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ModifyConnectionViewFactory.swift; sourceTree = ""; }; 48C158C8D1855BCE53636934 /* AccountCreateProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccountCreateProtocols.swift; sourceTree = ""; }; 48CECA2C5A0EFEBFDBB3C90C /* DAppOperationConfirmWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppOperationConfirmWireframe.swift; sourceTree = ""; }; - 48D6D4A250A171A3A22E3EDF /* DAppSearchInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppSearchInteractor.swift; sourceTree = ""; }; 4A191B92AD171FDDDD8C30E2 /* DAppSearchWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppSearchWireframe.swift; sourceTree = ""; }; 4B243F6751E2277D9FC14481 /* AdvancedWalletViewFactory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AdvancedWalletViewFactory.swift; sourceTree = ""; }; 4C0FA282377DCAB7C59ACFB6 /* RecommendedValidatorListViewController.xib */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.xib; path = RecommendedValidatorListViewController.xib; sourceTree = ""; }; @@ -2247,6 +2249,9 @@ 84293E61261BBFD7008E5EDD /* TransactionDetailsViewModelFactory+Transfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransactionDetailsViewModelFactory+Transfer.swift"; sourceTree = ""; }; 84293E66261BC50F008E5EDD /* TransactionDetailsViewModelFactory+RewardsAndSlashes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransactionDetailsViewModelFactory+RewardsAndSlashes.swift"; sourceTree = ""; }; 84293E6B261BCCF1008E5EDD /* TransactionDetailsViewModelFactory+Extrinsic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransactionDetailsViewModelFactory+Extrinsic.swift"; sourceTree = ""; }; + 842BA36A27B64F1000D31EEF /* DAppViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAppViewModel.swift; sourceTree = ""; }; + 842BA36B27B64F1000D31EEF /* DAppListState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAppListState.swift; sourceTree = ""; }; + 842BA36C27B64F1000D31EEF /* DAppListViewModelFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAppListViewModelFactory.swift; sourceTree = ""; }; 842BDB1D278C1CB100AB4B5A /* DAppBrowserStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppBrowserStateMachine.swift; sourceTree = ""; }; 842BDB20278C222100AB4B5A /* DAppBrowserBaseState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppBrowserBaseState.swift; sourceTree = ""; }; 842BDB22278C229D00AB4B5A /* DAppBrowserStateDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppBrowserStateDataSource.swift; sourceTree = ""; }; @@ -2580,9 +2585,6 @@ 846E5012277999FC0049B659 /* DAppAuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppAuthViewModel.swift; sourceTree = ""; }; 846E501427799B3E0049B659 /* DAppAuthViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppAuthViewModelFactory.swift; sourceTree = ""; }; 846E5017277AD3D50049B659 /* DAppList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppList.swift; sourceTree = ""; }; - 846E501A277ADB6B0049B659 /* DAppListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppListState.swift; sourceTree = ""; }; - 846E501C277ADB8C0049B659 /* DAppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppViewModel.swift; sourceTree = ""; }; - 846E501E277ADD280049B659 /* DAppListViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppListViewModelFactory.swift; sourceTree = ""; }; 8470D6CC253E3170009E9A5D /* AccountInfoSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountInfoSubscription.swift; sourceTree = ""; }; 8470D6CF253E321C009E9A5D /* StorageSubscriptionProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageSubscriptionProtocols.swift; sourceTree = ""; }; 8470D6D1253E3382009E9A5D /* StorageSubscriptionContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageSubscriptionContainer.swift; sourceTree = ""; }; @@ -2995,6 +2997,7 @@ 84CA68DE26BEAA0F003B9453 /* ChainModelMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainModelMapper.swift; sourceTree = ""; }; 84CA68E026BEAC7C003B9453 /* SpecVersionSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecVersionSubscriptionFactory.swift; sourceTree = ""; }; 84CB224F270360AC0041C8C1 /* StakingLocalSubscriptionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingLocalSubscriptionFactory.swift; sourceTree = ""; }; + 84CC128527B8336900C8E6AB /* AsyncClosureOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncClosureOperation.swift; sourceTree = ""; }; 84CCBFBB2509709500180F4F /* UIBarButtonItem+Style.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Style.swift"; sourceTree = ""; }; 84CD356F252620FB0081BC0B /* CryptoType+Extrinsic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CryptoType+Extrinsic.swift"; sourceTree = ""; }; 84CD82AD263C1452001A6F01 /* SubstrateProviderSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubstrateProviderSubscriber.swift; sourceTree = ""; }; @@ -3159,6 +3162,9 @@ 84EC2D17276B9DBC009B0BE1 /* PolkadotExtensionAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotExtensionAccount.swift; sourceTree = ""; }; 84EC2D19276C6600009B0BE1 /* PolkadotExtensionExtrinsic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotExtensionExtrinsic.swift; sourceTree = ""; }; 84EC2D1C276C684D009B0BE1 /* PolkadotExtensionSignerResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotExtensionSignerResult.swift; sourceTree = ""; }; + 84EE780127C4C2A40027357F /* DAppListFeaturedHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppListFeaturedHeaderView.swift; sourceTree = ""; }; + 84EE780327C4CF9E0027357F /* DAppListItemsLoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAppListItemsLoadingView.swift; sourceTree = ""; }; + 84EE780527C4DF130027357F /* DAppSearchInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppSearchInteractor.swift; sourceTree = ""; }; 84F0316E27302888003BDCF8 /* CoingeckoPriceListSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoingeckoPriceListSource.swift; sourceTree = ""; }; 84F13F0726F13898006725FF /* StakingAssetSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingAssetSettings.swift; sourceTree = ""; }; 84F13F0926F14122006725FF /* ChainAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainAsset.swift; sourceTree = ""; }; @@ -4789,6 +4795,16 @@ path = View; sourceTree = ""; }; + 842BA36927B64F1000D31EEF /* ViewModel */ = { + isa = PBXGroup; + children = ( + 842BA36A27B64F1000D31EEF /* DAppViewModel.swift */, + 842BA36B27B64F1000D31EEF /* DAppListState.swift */, + 842BA36C27B64F1000D31EEF /* DAppListViewModelFactory.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; 842BDB1F278C21FC00AB4B5A /* StateMachine */ = { isa = PBXGroup; children = ( @@ -5730,16 +5746,6 @@ path = PolkadotExtensionProtocol; sourceTree = ""; }; - 846E5019277ADB510049B659 /* ViewModel */ = { - isa = PBXGroup; - children = ( - 846E501A277ADB6B0049B659 /* DAppListState.swift */, - 846E501C277ADB8C0049B659 /* DAppViewModel.swift */, - 846E501E277ADD280049B659 /* DAppListViewModelFactory.swift */, - ); - path = ViewModel; - sourceTree = ""; - }; 8470D6CE253E31FB009E9A5D /* StorageSubscription */ = { isa = PBXGroup; children = ( @@ -6423,6 +6429,7 @@ 840D891C26242AE500AB231B /* StorageRequest.swift */, 84E6D57B262E2CE8000EA3F5 /* OperationCombiningService.swift */, AE2C32DB271D90DD00D57C91 /* MetaAccountOperationFactory.swift */, + 84CC128527B8336900C8E6AB /* AsyncClosureOperation.swift */, ); path = Operation; sourceTree = ""; @@ -7360,6 +7367,7 @@ 84DA03D9275A31A000E8B326 /* View */ = { isa = PBXGroup; children = ( + 84EE780327C4CF9E0027357F /* DAppListItemsLoadingView.swift */, 84DA03DA275A31B500E8B326 /* DAppListHeaderView.swift */, 8402CC9D275B946100E5BF30 /* DAppItemView.swift */, 8472072F277C335000F593DD /* DAppListFlowLayout.swift */, @@ -7367,6 +7375,7 @@ 8448D5B5277D717400FAEEBC /* DAppListDecorationView.swift */, 8448D5B7277DA4C200FAEEBC /* DAppListLoadingView.swift */, 84A04621277DE83E000B24DA /* DAppListErrorView.swift */, + 84EE780127C4C2A40027357F /* DAppListFeaturedHeaderView.swift */, ); path = View; sourceTree = ""; @@ -8457,10 +8466,10 @@ 8F96182151D003DF6789CB4B /* DAppSearchProtocols.swift */, 4A191B92AD171FDDDD8C30E2 /* DAppSearchWireframe.swift */, 329C58A0AE09361F5ECD6D4E /* DAppSearchPresenter.swift */, - 48D6D4A250A171A3A22E3EDF /* DAppSearchInteractor.swift */, 53235E51143C6E93303E30FE /* DAppSearchViewController.swift */, BE0F7D4389B932A1F2E17361 /* DAppSearchViewLayout.swift */, DBF9C192200F9B998724FC6C /* DAppSearchViewFactory.swift */, + 84EE780527C4DF130027357F /* DAppSearchInteractor.swift */, ); path = DAppSearch; sourceTree = ""; @@ -8651,7 +8660,7 @@ E06E44775A3129B62D29EC6E /* DAppList */ = { isa = PBXGroup; children = ( - 846E5019277ADB510049B659 /* ViewModel */, + 842BA36927B64F1000D31EEF /* ViewModel */, 84DA03D9275A31A000E8B326 /* View */, 10F3CCA5BA57C68D5AE2B42F /* DAppListProtocols.swift */, 3E6E41F045986002E1E26C12 /* DAppListWireframe.swift */, @@ -9791,10 +9800,10 @@ 8460E11F25420A2C00826F55 /* DetailsTriangularedView+Inspectable.swift in Sources */, AE8D028E2638074D00780D18 /* CIKeys.generated.swift in Sources */, F40966B726B297D6008CD244 /* AnalyticsContainerViewController.swift in Sources */, + 842BA36E27B64F1000D31EEF /* DAppListState.swift in Sources */, 84FD3DC02541B10B00A234E3 /* TransactionDetailsViewModelFactory.swift in Sources */, F4CE0FC727344F600094CD8A /* AcalaContributionConfirmProtocols.swift in Sources */, 8490145224A93FD1008F705E /* FearlessLoadingViewPresenter.swift in Sources */, - 846E501B277ADB6B0049B659 /* DAppListState.swift in Sources */, 844DBC71274E83F5009F8351 /* OnboardingMainBaseWireframe.swift in Sources */, 8490141A24A92F6D008F705E /* OnboardingMainProtocol.swift in Sources */, 84DBEA47265E98C300FDF73C /* AmountInputResult.swift in Sources */, @@ -9934,6 +9943,7 @@ 8472979A260B3095009B86D0 /* InitBondingSelectValidatorsStartWireframe.swift in Sources */, 84B73AD6279B4E0B0071AE16 /* AssetDetails.swift in Sources */, 843612BF278FE59D00DC739E /* DAppOperationConfirmInteractor+Proccessing.swift in Sources */, + 842BA36F27B64F1000D31EEF /* DAppListViewModelFactory.swift in Sources */, 841E6AF625EC12100007DDFE /* PreparedNomination.swift in Sources */, 848C3D0926248A3B005481C3 /* TransferCall.swift in Sources */, 84754C962511EF0D00854599 /* ManagedConnectionItemMapper.swift in Sources */, @@ -10024,6 +10034,7 @@ AEA2C1C02681E9EC0069492E /* ValidatorSearchViewLayout.swift in Sources */, 840DFDD8277061ED00B62981 /* DAppOperationConfirmModel.swift in Sources */, AE398392272AFB2B00BC8A85 /* WalletManagementViewFactory.swift in Sources */, + 84CC128627B8336900C8E6AB /* AsyncClosureOperation.swift in Sources */, 84F4393A25DAC48D00AEDA56 /* JSONRPCAliases.swift in Sources */, 843910D6253F8DAD00E3C217 /* NetworkAvailabilityLayerProtocols.swift in Sources */, 849842E626587573006BBB9F /* MultilineTableViewCell.swift in Sources */, @@ -10064,6 +10075,7 @@ 8490147524A94A37008F705E /* RootWireframe.swift in Sources */, 842876B424AE059700D91AD8 /* SocialMessage.swift in Sources */, 849014BC24AA87E4008F705E /* PinSetupInteractor.swift in Sources */, + 84EE780227C4C2A40027357F /* DAppListFeaturedHeaderView.swift in Sources */, 845BB8D125E45F1300E5FCDC /* NominateCall.swift in Sources */, 842BDB28278C4CE500AB4B5A /* DAppBrowserWaitingAuthState.swift in Sources */, 840689FC26321F2700A017B1 /* StorageQuery.swift in Sources */, @@ -10527,7 +10539,6 @@ 849014DE24AA8F60008F705E /* MainTabBarInteractor.swift in Sources */, 849ABE7226280F3800011A2A /* ControllersReducer.swift in Sources */, 84F30EE425FFAC0800039D09 /* StreamableProviderOptions+Substrate.swift in Sources */, - 846E501F277ADD280049B659 /* DAppListViewModelFactory.swift in Sources */, 8470D6D4253E35F0009E9A5D /* StorageUpdate.swift in Sources */, 84C2F27D25E297350050A4AD /* CalculatedReward.swift in Sources */, 849E689526AF388500E0E7BE /* ElectedValidatorInfo+Selected.swift in Sources */, @@ -10578,6 +10589,7 @@ 8494D860252470F400614D8F /* WalletFearlessFormDefining.swift in Sources */, AEA0C8AE267B818900F9666F /* SelectedValidatorListViewLayout.swift in Sources */, 84D8F15524D80CA100AF43E9 /* ModalPickerViewController.swift in Sources */, + 84EE780427C4CF9E0027357F /* DAppListItemsLoadingView.swift in Sources */, 84F43BAA25DEB75000AEDA56 /* StakingMainViewModel.swift in Sources */, 84DD5F35263D86EF00425ACF /* AccountFetching.swift in Sources */, 849AFEB726DBC0BE00B65924 /* AssetTransactionData+HistoryItemData.swift in Sources */, @@ -10695,6 +10707,7 @@ 847F2D4D27AA68DD00AFD476 /* WalletListAssetModel.swift in Sources */, 84BEE22325646AC000D05EB3 /* SelectedUsernameChanged.swift in Sources */, 8467FD4124ED3C72005D486C /* AlignableContentControl.swift in Sources */, + 842BA36D27B64F1000D31EEF /* DAppViewModel.swift in Sources */, 8439399C2636FAB40087658D /* YourValidatorViewModel.swift in Sources */, 841E2E4E2738159400F250C1 /* RemoteSubscriptionHandlingFactory.swift in Sources */, 846A2601267C768500429A7F /* CrowdloanContributionMapper.swift in Sources */, @@ -10945,6 +10958,7 @@ 0D6C27413F73190438306EC1 /* NetworkInfoInteractor.swift in Sources */, 8448221C26B1850D007F4492 /* TitleIconViewModel.swift in Sources */, F40966BC26B297D6008CD244 /* AnalyticsRewardsViewModelFactory.swift in Sources */, + 84EE780627C4DF130027357F /* DAppSearchInteractor.swift in Sources */, 2AFF4BA0274D1E4D00D790B4 /* UsernameSetupViewController.swift in Sources */, 843A2C6F26A8548B00266F53 /* RowView.swift in Sources */, 84EBAB06265DC24C0015E446 /* CrowdloanContributionViewModelFactory.swift in Sources */, @@ -11359,7 +11373,6 @@ FFE19A19E5B4ED67A2C61951 /* DAppSearchProtocols.swift in Sources */, F88D85C73094F6A1FC494D87 /* DAppSearchWireframe.swift in Sources */, 6B393DCF67CF97FDA580C69B /* DAppSearchPresenter.swift in Sources */, - 4C00E26598A13EB86B702A0E /* DAppSearchInteractor.swift in Sources */, DC682E96D056C069902B9C31 /* DAppSearchViewController.swift in Sources */, 286577D8FE44D1F9E7BBDCA9 /* DAppSearchViewLayout.swift in Sources */, E325FBFE10A037E58525DA66 /* DAppSearchViewFactory.swift in Sources */, @@ -11385,7 +11398,6 @@ 9BADFCBF3AF5186094DB8D67 /* DAppTxDetailsInteractor.swift in Sources */, B409644ED1E20062A3EA0316 /* DAppTxDetailsViewController.swift in Sources */, DAD46B2B29A446C19A6ABF2D /* DAppTxDetailsViewLayout.swift in Sources */, - 846E501D277ADB8C0049B659 /* DAppViewModel.swift in Sources */, A97F32D057BFEFBCC478A09C /* DAppTxDetailsViewFactory.swift in Sources */, D567BAAF620EDB9F4975C800 /* DAppAuthConfirmProtocols.swift in Sources */, F0B74A766BF50518323AB25C /* DAppAuthConfirmWireframe.swift in Sources */, @@ -11814,6 +11826,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon$(ICON_SUFFIX)"; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = 27GUNVKXNM; ENABLE_BITCODE = NO; INFOPLIST_FILE = novawallet/Info.plist; @@ -11824,7 +11837,7 @@ "$(CONFIGURATION_BUILD_DIR)", "$(FRAMEWORK_SEARCH_PATHS)", ); - MARKETING_VERSION = 2.1; + MARKETING_VERSION = 2.4.4; OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = io.nova.novawallet.dev; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -11841,7 +11854,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon$(ICON_SUFFIX)"; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = 27GUNVKXNM; + CURRENT_PROJECT_VERSION = 0; + DEVELOPMENT_TEAM = RD2HBGJZLF; ENABLE_BITCODE = NO; INFOPLIST_FILE = novawallet/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -11851,10 +11865,10 @@ "$(CONFIGURATION_BUILD_DIR)", "$(FRAMEWORK_SEARCH_PATHS)", ); - MARKETING_VERSION = 2.1; - PRODUCT_BUNDLE_IDENTIFIER = io.nova.novawallet; + MARKETING_VERSION = 2.4.4; + PRODUCT_BUNDLE_IDENTIFIER = io.novafoundation.novawallet; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "nova-wallet"; + PROVISIONING_PROFILE_SPECIFIER = "nova-wallet-appstore"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; @@ -11977,6 +11991,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon$(ICON_SUFFIX)"; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = 27GUNVKXNM; ENABLE_BITCODE = NO; INFOPLIST_FILE = novawallet/Info.plist; @@ -11987,7 +12002,7 @@ "$(CONFIGURATION_BUILD_DIR)", "$(FRAMEWORK_SEARCH_PATHS)", ); - MARKETING_VERSION = 2.1; + MARKETING_VERSION = 2.4.4; OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = io.nova.novawallet.dev; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -12084,6 +12099,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon$(ICON_SUFFIX)"; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = 27GUNVKXNM; ENABLE_BITCODE = NO; INFOPLIST_FILE = novawallet/Info.plist; @@ -12094,7 +12110,7 @@ "$(CONFIGURATION_BUILD_DIR)", "$(FRAMEWORK_SEARCH_PATHS)", ); - MARKETING_VERSION = 2.1; + MARKETING_VERSION = 2.4.4; PRODUCT_BUNDLE_IDENTIFIER = io.nova.novawallet.staging; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "nova-wallet-staging"; diff --git a/novawallet/Assets.xcassets/iconsTabBar/iconTabDApps.imageset/Contents.json b/novawallet/Assets.xcassets/iconsTabBar/iconTabDApps.imageset/Contents.json index e3c11f4012..c4ce042724 100644 --- a/novawallet/Assets.xcassets/iconsTabBar/iconTabDApps.imageset/Contents.json +++ b/novawallet/Assets.xcassets/iconsTabBar/iconTabDApps.imageset/Contents.json @@ -8,8 +8,5 @@ "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" } } diff --git a/novawallet/Assets.xcassets/iconsTabBar/iconTabDApps.imageset/iconTabDApps.pdf b/novawallet/Assets.xcassets/iconsTabBar/iconTabDApps.imageset/iconTabDApps.pdf index 52b9c42f80..1836a8114c 100644 Binary files a/novawallet/Assets.xcassets/iconsTabBar/iconTabDApps.imageset/iconTabDApps.pdf and b/novawallet/Assets.xcassets/iconsTabBar/iconTabDApps.imageset/iconTabDApps.pdf differ diff --git a/novawallet/Assets.xcassets/iconsTabBar/iconTabDAppsFilled.imageset/iconTabDAppsFilled.pdf b/novawallet/Assets.xcassets/iconsTabBar/iconTabDAppsFilled.imageset/iconTabDAppsFilled.pdf index 4f8ef3180c..8e94c2d485 100644 Binary files a/novawallet/Assets.xcassets/iconsTabBar/iconTabDAppsFilled.imageset/iconTabDAppsFilled.pdf and b/novawallet/Assets.xcassets/iconsTabBar/iconTabDAppsFilled.imageset/iconTabDAppsFilled.pdf differ diff --git a/novawallet/Common/Operation/AsyncClosureOperation.swift b/novawallet/Common/Operation/AsyncClosureOperation.swift new file mode 100644 index 0000000000..622b47294f --- /dev/null +++ b/novawallet/Common/Operation/AsyncClosureOperation.swift @@ -0,0 +1,58 @@ +import Foundation +import RobinHood + +final class AsyncClosureOperation: BaseOperation { + let operationClosure: (@escaping (Result?) -> Void) throws -> Void + let cancelationClosure: () -> Void + + private var mutex: DispatchSemaphore? + + public init( + cancelationClosure: @escaping () -> Void, + operationClosure: @escaping (@escaping (Result?) -> Void) throws -> Void + ) { + self.cancelationClosure = cancelationClosure + self.operationClosure = operationClosure + } + + override public func main() { + super.main() + + if isCancelled { + return + } + + if result != nil { + return + } + + mutex = DispatchSemaphore(value: 0) + + do { + var closureResult: Result? + + try operationClosure { [weak self] operationResult in + closureResult = operationResult + + self?.mutex?.signal() + } + + if let result = closureResult { + self.result = result + } else { + mutex?.wait() + + result = closureResult + } + + } catch { + result = .failure(error) + } + } + + override func cancel() { + super.cancel() + + cancelationClosure() + } +} diff --git a/novawallet/Common/Operation/StorageDecodingOperation.swift b/novawallet/Common/Operation/StorageDecodingOperation.swift index 1984d3edfa..9021a45a17 100644 --- a/novawallet/Common/Operation/StorageDecodingOperation.swift +++ b/novawallet/Common/Operation/StorageDecodingOperation.swift @@ -235,8 +235,11 @@ final class StorageConstantOperation: BaseOperation, ConstantDe let path: ConstantCodingPath - init(path: ConstantCodingPath) { + let fallbackValue: T? + + init(path: ConstantCodingPath, fallbackValue: T? = nil) { self.path = path + self.fallbackValue = fallbackValue super.init() } @@ -260,7 +263,14 @@ final class StorageConstantOperation: BaseOperation, ConstantDe let item: T = try decode(at: path, codingFactory: factory).map(to: T.self) result = .success(item) } catch { - result = .failure(error) + if + let storageError = error as? StorageDecodingOperationError, + storageError == .invalidStoragePath, + let fallbackValue = fallbackValue { + result = .success(fallbackValue) + } else { + result = .failure(error) + } } } } @@ -270,8 +280,11 @@ final class PrimitiveConstantOperation let path: ConstantCodingPath - init(path: ConstantCodingPath) { + let fallbackValue: T? + + init(path: ConstantCodingPath, fallbackValue: T? = nil) { self.path = path + self.fallbackValue = fallbackValue super.init() } @@ -296,7 +309,14 @@ final class PrimitiveConstantOperation .map(to: StringScaleMapper.self) result = .success(item.value) } catch { - result = .failure(error) + if + let storageError = error as? StorageDecodingOperationError, + storageError == .invalidStoragePath, + let fallbackValue = fallbackValue { + result = .success(fallbackValue) + } else { + result = .failure(error) + } } } } diff --git a/novawallet/Common/Protocols/RuntimeConstantFetching.swift b/novawallet/Common/Protocols/RuntimeConstantFetching.swift index c74c8742fa..f51f32954d 100644 --- a/novawallet/Common/Protocols/RuntimeConstantFetching.swift +++ b/novawallet/Common/Protocols/RuntimeConstantFetching.swift @@ -6,6 +6,7 @@ protocol RuntimeConstantFetching { for path: ConstantCodingPath, runtimeCodingService: RuntimeCodingServiceProtocol, operationManager: OperationManagerProtocol, + fallbackValue: T?, closure: @escaping (Result) -> Void ) -> CancellableCall @@ -13,6 +14,7 @@ protocol RuntimeConstantFetching { for path: ConstantCodingPath, runtimeCodingService: RuntimeCodingServiceProtocol, operationManager: OperationManagerProtocol, + fallbackValue: T?, closure: @escaping (Result) -> Void ) -> CancellableCall } @@ -24,9 +26,42 @@ extension RuntimeConstantFetching { runtimeCodingService: RuntimeCodingServiceProtocol, operationManager: OperationManagerProtocol, closure: @escaping (Result) -> Void + ) -> CancellableCall { + fetchConstant( + for: path, + runtimeCodingService: runtimeCodingService, + operationManager: operationManager, + fallbackValue: nil, + closure: closure + ) + } + + @discardableResult + func fetchCompoundConstant( + for path: ConstantCodingPath, + runtimeCodingService: RuntimeCodingServiceProtocol, + operationManager: OperationManagerProtocol, + closure: @escaping (Result) -> Void + ) -> CancellableCall { + fetchCompoundConstant( + for: path, + runtimeCodingService: runtimeCodingService, + operationManager: operationManager, + fallbackValue: nil, + closure: closure + ) + } + + @discardableResult + func fetchConstant( + for path: ConstantCodingPath, + runtimeCodingService: RuntimeCodingServiceProtocol, + operationManager: OperationManagerProtocol, + fallbackValue: T?, + closure: @escaping (Result) -> Void ) -> CancellableCall { let codingFactoryOperation = runtimeCodingService.fetchCoderFactoryOperation() - let constOperation = PrimitiveConstantOperation(path: path) + let constOperation = PrimitiveConstantOperation(path: path, fallbackValue: fallbackValue) constOperation.configurationBlock = { do { constOperation.codingFactory = try codingFactoryOperation.extractNoCancellableResultData() @@ -57,10 +92,11 @@ extension RuntimeConstantFetching { for path: ConstantCodingPath, runtimeCodingService: RuntimeCodingServiceProtocol, operationManager: OperationManagerProtocol, + fallbackValue: T?, closure: @escaping (Result) -> Void ) -> CancellableCall { let codingFactoryOperation = runtimeCodingService.fetchCoderFactoryOperation() - let constOperation = StorageConstantOperation(path: path) + let constOperation = StorageConstantOperation(path: path, fallbackValue: fallbackValue) constOperation.configurationBlock = { do { constOperation.codingFactory = try codingFactoryOperation.extractNoCancellableResultData() diff --git a/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeProvider.swift b/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeProvider.swift index 2e439215d2..4265d9ae96 100644 --- a/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeProvider.swift +++ b/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeProvider.swift @@ -30,7 +30,7 @@ final class RuntimeProvider { let logger: LoggerProtocol? private(set) var snapshot: RuntimeSnapshot? - private(set) var pendingRequests: [PendingRequest] = [] + private(set) var pendingRequests: [UUID: PendingRequest] = [:] private(set) var currentWrapper: CompoundOperationWrapper? private var mutex = NSLock() @@ -104,15 +104,30 @@ final class RuntimeProvider { } } + private func cancelRequest(for requestId: UUID) { + mutex.lock() + + defer { + mutex.unlock() + } + + let maybePendingRequest = pendingRequests[requestId] + pendingRequests[requestId] = nil + + if let pendingRequest = maybePendingRequest { + deliver(snapshot: nil, to: pendingRequest) + } + } + private func resolveRequests() { guard !pendingRequests.isEmpty else { return } let requests = pendingRequests - pendingRequests = [] + pendingRequests = [:] - requests.forEach { deliver(snapshot: snapshot, to: $0) } + requests.forEach { deliver(snapshot: snapshot, to: $0.value) } } private func deliver(snapshot: RuntimeSnapshot?, to request: PendingRequest) { @@ -131,6 +146,7 @@ final class RuntimeProvider { } private func fetchCoderFactory( + assigning requestId: UUID, runCompletionIn queue: DispatchQueue?, executing closure: @escaping (RuntimeCoderFactoryProtocol?) -> Void ) { @@ -145,28 +161,27 @@ final class RuntimeProvider { if let snapshot = snapshot { deliver(snapshot: snapshot, to: request) } else { - pendingRequests.append(request) + pendingRequests[requestId] = request } } func fetchCoderFactoryOperation() -> BaseOperation { - ClosureOperation { [weak self] in - var fetchedFactory: RuntimeCoderFactoryProtocol? - - let semaphore = DispatchSemaphore(value: 0) - - self?.fetchCoderFactory(runCompletionIn: nil) { factory in - fetchedFactory = factory - semaphore.signal() + let requestId = UUID() + + return AsyncClosureOperation(cancelationClosure: { [weak self] in + self?.cancelRequest(for: requestId) + }) { [weak self] responseClosure in + if let strongSelf = self { + strongSelf.fetchCoderFactory(assigning: requestId, runCompletionIn: nil) { maybeFactory in + if let factory = maybeFactory { + responseClosure(.success(factory)) + } else { + responseClosure(nil) + } + } + } else { + responseClosure(.failure(RuntimeProviderError.providerUnavailable)) } - - semaphore.wait() - - guard let factory = fetchedFactory else { - throw RuntimeProviderError.providerUnavailable - } - - return factory } } } diff --git a/novawallet/Common/Services/ExtrinsicService/MortalEraOperationFactory.swift b/novawallet/Common/Services/ExtrinsicService/MortalEraOperationFactory.swift index 325b770822..80613b1662 100644 --- a/novawallet/Common/Services/ExtrinsicService/MortalEraOperationFactory.swift +++ b/novawallet/Common/Services/ExtrinsicService/MortalEraOperationFactory.swift @@ -87,14 +87,17 @@ final class MortalEraOperationFactory { guard let bestNumber = BigUInt.fromHexString(bestHeader.number), - let finalizedNumber = BigUInt.fromHexString(finalizedHeader.number), - bestNumber >= finalizedNumber else { + let finalizedNumber = BigUInt.fromHexString(finalizedHeader.number) else { throw BaseOperationError.unexpectedDependentResult } - let blockNumber = bestNumber - finalizedNumber > Self.maxFinalityLag ? bestNumber : finalizedNumber + if bestNumber >= finalizedNumber { + let blockNumber = bestNumber - finalizedNumber > Self.maxFinalityLag ? bestNumber : finalizedNumber - return BlockNumber(blockNumber) + return BlockNumber(blockNumber) + } else { + return BlockNumber(finalizedNumber) + } } mapOperation.addDependency(finalizedHeaderWrapper.targetOperation) diff --git a/novawallet/Common/Services/PayoutRewardsService/PayoutValidatorsForNominatorFactory.swift b/novawallet/Common/Services/PayoutRewardsService/PayoutValidatorsForNominatorFactory.swift index ce76f52ad2..1f1212fd33 100644 --- a/novawallet/Common/Services/PayoutRewardsService/PayoutValidatorsForNominatorFactory.swift +++ b/novawallet/Common/Services/PayoutRewardsService/PayoutValidatorsForNominatorFactory.swift @@ -60,6 +60,7 @@ final class PayoutValidatorsForNominatorFactory { { query { eraValidatorInfos( + orderBy: ERA_DESC, filter:{ \(eraFilter) others:{contains:[{who:\"\(accountAddress)\"}]} diff --git a/novawallet/Info.plist b/novawallet/Info.plist index 063719ea74..d9cfc8a3a4 100644 --- a/novawallet/Info.plist +++ b/novawallet/Info.plist @@ -45,7 +45,7 @@ CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace diff --git a/novawallet/Modules/DApp/DAppList/DAppListViewController.swift b/novawallet/Modules/DApp/DAppList/DAppListViewController.swift index 0b8288f069..74743279a2 100644 --- a/novawallet/Modules/DApp/DAppList/DAppListViewController.swift +++ b/novawallet/Modules/DApp/DAppList/DAppListViewController.swift @@ -63,10 +63,11 @@ final class DAppListViewController: UIViewController, ViewHolder { private func configureCollectionView() { rootView.collectionView.registerCellClass(DAppListHeaderView.self) - rootView.collectionView.registerCellClass(DAppCategoriesView.self) rootView.collectionView.registerCellClass(DAppListLoadingView.self) + rootView.collectionView.registerCellClass(DAppCategoriesView.self) rootView.collectionView.registerCellClass(DAppListErrorView.self) rootView.collectionView.registerCellClass(DAppItemView.self) + rootView.collectionView.registerCellClass(DAppListFeaturedHeaderView.self) collectionViewLayout?.register( DAppListDecorationView.self, @@ -111,7 +112,7 @@ extension DAppListViewController: UICollectionViewDelegate { } switch cellType { - case .header, .notLoaded, .categories: + case .header, .notLoaded, .dAppHeader, .categories: break case let .dapp(index): presenter.selectDApp(at: index) @@ -172,12 +173,21 @@ extension DAppListViewController: UICollectionViewDataSource { return view } + private func setupDAppHeaderView( + using collectionView: UICollectionView, + indexPath: IndexPath + ) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCellWithType(DAppListFeaturedHeaderView.self, for: indexPath)! + cell.locale = selectedLocale + + return cell + } + private func setupLoadingView( using collectionView: UICollectionView, indexPath: IndexPath ) -> UICollectionViewCell { let view = collectionView.dequeueReusableCellWithType(DAppListLoadingView.self, for: indexPath)! - view.selectedLocale = selectedLocale return view } @@ -221,6 +231,8 @@ extension DAppListViewController: UICollectionViewDataSource { return setupHeaderView(using: collectionView, indexPath: indexPath) case .notLoaded: return setupLoadingOrErrorView(using: collectionView, indexPath: indexPath) + case .dAppHeader: + return setupDAppHeaderView(using: collectionView, indexPath: indexPath) case .categories: return setupCategoriesView(using: collectionView, indexPath: indexPath) case .dapp: @@ -243,9 +255,9 @@ extension DAppListViewController: UICollectionViewDataSource { if section == DAppListFlowLayout.CellType.header.indexPath.section { switch state { case .error, .loading: - return 2 + return 3 case .loaded: - return 1 + return 2 case .none: return 0 } @@ -255,18 +267,18 @@ extension DAppListViewController: UICollectionViewDataSource { } } -extension DAppListViewController: DAppCategoriesViewDelegate { - func dAppCategories(view _: DAppCategoriesView, didSelectItemAt index: Int) { - presenter.selectCategory(at: index) - } -} - extension DAppListViewController: ErrorStateViewDelegate { func didRetry(errorView _: ErrorStateView) { presenter.refresh() } } +extension DAppListViewController: DAppCategoriesViewDelegate { + func dAppCategories(view _: DAppCategoriesView, didSelectItemAt index: Int) { + presenter.selectCategory(at: index) + } +} + extension DAppListViewController: DAppListViewProtocol { func didReceiveAccount(icon: DrawableIcon) { let iconSize = CGSize( diff --git a/novawallet/Modules/DApp/DAppList/View/DAppListFeaturedHeaderView.swift b/novawallet/Modules/DApp/DAppList/View/DAppListFeaturedHeaderView.swift new file mode 100644 index 0000000000..39945bcaab --- /dev/null +++ b/novawallet/Modules/DApp/DAppList/View/DAppListFeaturedHeaderView.swift @@ -0,0 +1,54 @@ +import UIKit + +final class DAppListFeaturedHeaderView: UICollectionViewCell { + static let preferredHeight: CGFloat = 64.0 + + let titleLabel: UILabel = { + let view = UILabel() + view.font = .semiBoldTitle3 + view.textColor = R.color.colorWhite() + return view + }() + + var locale = Locale.current { + didSet { + if oldValue != locale { + setupLocalization() + } + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + setupLayout() + setupLocalization() + } + + override func preferredLayoutAttributesFitting( + _ layoutAttributes: UICollectionViewLayoutAttributes + ) -> UICollectionViewLayoutAttributes { + layoutAttributes.frame.size = CGSize(width: layoutAttributes.frame.width, height: Self.preferredHeight) + return layoutAttributes + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupLocalization() { + titleLabel.text = R.string.localizable.dappListFeaturedWebsites( + preferredLanguages: locale.rLanguages + ) + } + + private func setupLayout() { + contentView.addSubview(titleLabel) + + titleLabel.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(UIConstants.horizontalInset) + make.centerY.equalToSuperview() + } + } +} diff --git a/novawallet/Modules/DApp/DAppList/View/DAppListFlowLayout.swift b/novawallet/Modules/DApp/DAppList/View/DAppListFlowLayout.swift index bf58f773aa..a79b6885a9 100644 --- a/novawallet/Modules/DApp/DAppList/View/DAppListFlowLayout.swift +++ b/novawallet/Modules/DApp/DAppList/View/DAppListFlowLayout.swift @@ -5,6 +5,7 @@ final class DAppListFlowLayout: UICollectionViewFlowLayout { enum CellType { case header case notLoaded + case dAppHeader case categories case dapp(index: Int) @@ -14,6 +15,8 @@ final class DAppListFlowLayout: UICollectionViewFlowLayout { case 0: self = .header case 1: + self = .dAppHeader + case 2: self = .notLoaded default: return nil @@ -34,8 +37,10 @@ final class DAppListFlowLayout: UICollectionViewFlowLayout { switch self { case .header: return IndexPath(item: 0, section: 0) - case .notLoaded: + case .dAppHeader: return IndexPath(item: 1, section: 0) + case .notLoaded: + return IndexPath(item: 2, section: 0) case .categories: return IndexPath(item: 0, section: 1) case let .dapp(index): @@ -112,13 +117,12 @@ final class DAppListFlowLayout: UICollectionViewFlowLayout { } private func updateItemsBackgroundAttributesIfNeeded() { - guard - let collectionView = collectionView, - collectionView.numberOfSections > CellType.categories.indexPath.section else { + let dAppSection = CellType.categories.indexPath.section + guard let collectionView = collectionView, collectionView.numberOfSections > dAppSection else { return } - let numberOfItems = collectionView.numberOfItems(inSection: CellType.categories.indexPath.section) + let numberOfItems = collectionView.numberOfItems(inSection: dAppSection) guard numberOfItems > 0 else { return @@ -126,7 +130,7 @@ final class DAppListFlowLayout: UICollectionViewFlowLayout { guard let headerLayoutFrame = collectionView.layoutAttributesForItem( - at: CellType.header.indexPath + at: CellType.dAppHeader.indexPath )?.frame, headerUsedFrame != headerLayoutFrame else { @@ -140,7 +144,7 @@ final class DAppListFlowLayout: UICollectionViewFlowLayout { itemsDecorationAttributes = UICollectionViewLayoutAttributes( forDecorationViewOfKind: Self.backgroundDecoration, - with: IndexPath(item: 0, section: CellType.categories.indexPath.section) + with: IndexPath(item: 0, section: dAppSection) ) let size = CGSize( diff --git a/novawallet/Modules/DApp/DAppList/View/DAppListHeaderView.swift b/novawallet/Modules/DApp/DAppList/View/DAppListHeaderView.swift index 0d974e38ef..33cc55b116 100644 --- a/novawallet/Modules/DApp/DAppList/View/DAppListHeaderView.swift +++ b/novawallet/Modules/DApp/DAppList/View/DAppListHeaderView.swift @@ -89,15 +89,15 @@ final class DAppListHeaderView: UICollectionViewCell { } private func setupLocalization() { - titleLabel.text = R.string.localizable.tabbarDappsTitle( + titleLabel.text = R.string.localizable.tabbarDappsTitle_2_4_3( preferredLanguages: selectedLocale.rLanguages ) - decorationTitleLabel.text = R.string.localizable.dappDecorationTitle( + decorationTitleLabel.text = R.string.localizable.dappDecorationTitle_2_4_3( preferredLanguages: selectedLocale.rLanguages ) - decorationSubtitleLabel.text = R.string.localizable.dappsDecorationSubtitle( + decorationSubtitleLabel.text = R.string.localizable.dappsDecorationSubtitle_2_4_3( preferredLanguages: selectedLocale.rLanguages ) @@ -145,7 +145,7 @@ final class DAppListHeaderView: UICollectionViewCell { make.leading.trailing.equalToSuperview().inset(UIConstants.horizontalInset) make.top.equalTo(decorationView.snp.bottom).offset(12.0) make.height.equalTo(52.0) - make.bottom.equalToSuperview().inset(12.0) + make.bottom.equalToSuperview().inset(0.0) } } } diff --git a/novawallet/Modules/DApp/DAppList/View/DAppListItemsLoadingView.swift b/novawallet/Modules/DApp/DAppList/View/DAppListItemsLoadingView.swift new file mode 100644 index 0000000000..5d179f5f67 --- /dev/null +++ b/novawallet/Modules/DApp/DAppList/View/DAppListItemsLoadingView.swift @@ -0,0 +1,149 @@ +import UIKit +import SoraUI + +final class DAppListItemsLoadingView: UICollectionViewCell { + static let preferredHeight: CGFloat = 274.0 + + let listBackgroundView = TriangularedBlurView() + + private var skeletonView: SkrullableView? + + override init(frame: CGRect) { + super.init(frame: frame) + + contentView.addSubview(listBackgroundView) + + setupSkeleton() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func preferredLayoutAttributesFitting( + _ layoutAttributes: UICollectionViewLayoutAttributes + ) -> UICollectionViewLayoutAttributes { + layoutAttributes.frame.size = CGSize(width: layoutAttributes.frame.width, height: Self.preferredHeight) + return layoutAttributes + } + + override func layoutSubviews() { + super.layoutSubviews() + + listBackgroundView.frame = CGRect( + x: UIConstants.horizontalInset, + y: 0.0, + width: bounds.width - 2 * UIConstants.horizontalInset, + height: bounds.height + ) + + setupSkeleton() + } + + private func setupSkeleton() { + let spaceSize = CGSize(width: listBackgroundView.frame.width, height: Self.preferredHeight) + + guard spaceSize.width > 0, spaceSize.height > 0 else { + return + } + + let builder = Skrull(size: spaceSize, decorations: [], skeletons: createSkeletons(for: spaceSize)) + + let currentSkeletonView: SkrullableView? + + if let skeletonView = skeletonView { + currentSkeletonView = skeletonView + builder.updateSkeletons(in: skeletonView) + } else { + let view = builder + .fillSkeletonStart(R.color.colorSkeletonStart()!) + .fillSkeletonEnd(color: R.color.colorSkeletonEnd()!) + .build() + view.autoresizingMask = [] + view.clipsToBounds = true + listBackgroundView.addSubview(view) + + skeletonView = view + + view.startSkrulling() + + currentSkeletonView = view + } + + currentSkeletonView?.frame = CGRect(origin: .zero, size: spaceSize) + } + + private func createSkeletons(for size: CGSize) -> [Skeletonable] { + createCellSkeletons(for: size) + } + + private func createCellSkeletons(for size: CGSize) -> [Skeletonable] { + let iconSize = CGSize(width: 48.0, height: 48.0) + let titleSize = CGSize(width: 66.0, height: 12.0) + let subtitleSize = CGSize(width: 120.0, height: 8.0) + + let offsetX = UIConstants.horizontalInset + let offsetY = 16.0 + let spacing = 16.0 + + let compoundSkeletons: [[Skeletonable]] = (0 ..< 4).map { index in + let iconOffset = CGPoint( + x: offsetX, + y: offsetY + CGFloat(index) * (iconSize.height + spacing) + ) + + let iconSkeleton = SingleSkeleton.createRow( + on: listBackgroundView, + containerView: listBackgroundView, + spaceSize: size, + offset: iconOffset, + size: iconSize + ) + + let titleOffset = CGPoint( + x: iconOffset.x + iconSize.width + 12.0, + y: iconOffset.y + 8.0 + ) + + let titleSkeleton = SingleSkeleton.createRow( + on: listBackgroundView, + containerView: listBackgroundView, + spaceSize: size, + offset: titleOffset, + size: titleSize + ) + + let subtitleOffset = CGPoint( + x: iconOffset.x + iconSize.width + 12.0, + y: titleOffset.y + titleSize.height + 10.0 + ) + + let subtitleSkeleton = SingleSkeleton.createRow( + on: listBackgroundView, + containerView: listBackgroundView, + spaceSize: size, + offset: subtitleOffset, + size: subtitleSize + ) + + return [iconSkeleton, titleSkeleton, subtitleSkeleton] + } + + return compoundSkeletons.flatMap { $0 } + } +} + +extension DAppListItemsLoadingView: SkeletonLoadable { + func didDisappearSkeleton() { + skeletonView?.stopSkrulling() + } + + func didAppearSkeleton() { + skeletonView?.startSkrulling() + } + + func didUpdateSkeletonLayout() { + setupSkeleton() + } +} diff --git a/novawallet/Modules/DApp/DAppSearch/DAppSearchProtocols.swift b/novawallet/Modules/DApp/DAppSearch/DAppSearchProtocols.swift index aa0f1c6cf2..551f67d260 100644 --- a/novawallet/Modules/DApp/DAppSearch/DAppSearchProtocols.swift +++ b/novawallet/Modules/DApp/DAppSearch/DAppSearchProtocols.swift @@ -1,4 +1,5 @@ import RobinHood + protocol DAppSearchViewProtocol: ControllerBackedProtocol { func didReceive(initialQuery: String) func didReceiveDApp(viewModels: [DAppViewModel]) diff --git a/novawallet/Modules/DApp/DAppSearch/DAppSearchViewController.swift b/novawallet/Modules/DApp/DAppSearch/DAppSearchViewController.swift index ddeaabf16f..10e280b49c 100644 --- a/novawallet/Modules/DApp/DAppSearch/DAppSearchViewController.swift +++ b/novawallet/Modules/DApp/DAppSearch/DAppSearchViewController.swift @@ -172,7 +172,7 @@ extension DAppSearchViewController: UITableViewDataSource { preferredLanguages: selectedLocale.rLanguages ) case .dapps: - title = R.string.localizable.dappSearchAppSection( + title = R.string.localizable.dappListFeaturedWebsites( preferredLanguages: selectedLocale.rLanguages ) } diff --git a/novawallet/Modules/MainTabBar/MainTabBarViewFactory.swift b/novawallet/Modules/MainTabBar/MainTabBarViewFactory.swift index 90ac529403..e69927a3c1 100644 --- a/novawallet/Modules/MainTabBar/MainTabBarViewFactory.swift +++ b/novawallet/Modules/MainTabBar/MainTabBarViewFactory.swift @@ -242,7 +242,7 @@ final class MainTabBarViewFactory: MainTabBarViewFactoryProtocol { let navigationController = FearlessNavigationController(rootViewController: dappsView.controller) let localizableTitle = LocalizableResource { locale in - R.string.localizable.tabbarDappsTitle(preferredLanguages: locale.rLanguages) + R.string.localizable.tabbarDappsTitle_2_4_3(preferredLanguages: locale.rLanguages) } let currentTitle = localizableTitle.value(for: localizationManager.selectedLocale) diff --git a/novawallet/Modules/Staking/SelectValidatorsFlow/SelectValidatorsStart/SelectValidatorsStartInteractor.swift b/novawallet/Modules/Staking/SelectValidatorsFlow/SelectValidatorsStart/SelectValidatorsStartInteractor.swift index a66cb7f693..bcac56531a 100644 --- a/novawallet/Modules/Staking/SelectValidatorsFlow/SelectValidatorsStart/SelectValidatorsStartInteractor.swift +++ b/novawallet/Modules/Staking/SelectValidatorsFlow/SelectValidatorsStart/SelectValidatorsStartInteractor.swift @@ -36,18 +36,22 @@ final class SelectValidatorsStartInteractor: RuntimeConstantFetching { operationManager.enqueue(operations: wrapper.allOperations, in: .transient) } -} - -extension SelectValidatorsStartInteractor: SelectValidatorsStartInteractorInputProtocol { - func setup() { - prepareRecommendedValidatorList() + private func provideMaxNominations() { fetchConstant( for: .maxNominations, runtimeCodingService: runtimeService, - operationManager: operationManager + operationManager: operationManager, + fallbackValue: SubstrateConstants.maxNominations ) { [weak self] (result: Result) in self?.presenter.didReceiveMaxNominations(result: result) } } } + +extension SelectValidatorsStartInteractor: SelectValidatorsStartInteractorInputProtocol { + func setup() { + prepareRecommendedValidatorList() + provideMaxNominations() + } +} diff --git a/novawallet/Modules/WalletList/WalletListPresenter+Model.swift b/novawallet/Modules/WalletList/WalletListPresenter+Model.swift index 4200c6bcae..9a55337276 100644 --- a/novawallet/Modules/WalletList/WalletListPresenter+Model.swift +++ b/novawallet/Modules/WalletList/WalletListPresenter+Model.swift @@ -16,7 +16,7 @@ extension WalletListPresenter { static func createGroupsDiffCalculator( from groups: [WalletListGroupModel] ) -> ListDifferenceCalculator { - ListDifferenceCalculator(initialItems: groups) { model1, model2 in + let sortingBlock: (WalletListGroupModel, WalletListGroupModel) -> Bool = { model1, model2 in if model1.chainValue > 0, model2.chainValue > 0 { return model1.chainValue > model2.chainValue } else if model1.chainValue > 0 { @@ -27,12 +27,16 @@ extension WalletListPresenter { return model1.chain.name.lexicographicallyPrecedes(model2.chain.name) } } + + let sortedGroups = groups.sorted(by: sortingBlock) + + return ListDifferenceCalculator(initialItems: sortedGroups, sortBlock: sortingBlock) } static func createAssetsDiffCalculator( from assets: [WalletListAssetModel] ) -> ListDifferenceCalculator { - ListDifferenceCalculator(initialItems: assets) { model1, model2 in + let sortingBlock: (WalletListAssetModel, WalletListAssetModel) -> Bool = { model1, model2 in let balance1 = (try? model1.balanceResult?.get()) ?? 0 let balance2 = (try? model2.balanceResult?.get()) ?? 0 @@ -55,6 +59,10 @@ extension WalletListPresenter { return model1.assetModel.symbol.lexicographicallyPrecedes(model2.assetModel.symbol) } } + + let sortedAssets = assets.sorted(by: sortingBlock) + + return ListDifferenceCalculator(initialItems: sortedAssets, sortBlock: sortingBlock) } func createAssetModels(for chainModel: ChainModel) -> [WalletListAssetModel] { diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index be965c524e..4c5a2f1e43 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -673,4 +673,8 @@ "wallet.send.dead.recipient.commission.asset.message" = "Your transfer will fail since the destination account does not have enough %@ to accept other token transfers"; "common.era.format" = "#%@"; "wallet.total.balance" = "Total balance"; -"assets.manage.title" = "Filters"; \ No newline at end of file +"assets.manage.title" = "Filters"; +"tabbar.dapps.title_2_4_3" = "Browser"; +"dapps.decoration.subtitle_2_4_3" = "Browse the web using\n your accounts from Nova Wallet"; +"dapp.decoration.title_2_4_3" = "Welcome\nto Nova browser"; +"dapp.list.featured.websites" = "Featured websites"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 21d016108a..78038d1cbe 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -673,4 +673,8 @@ "wallet.send.dead.recipient.commission.asset.message" = "Ваш перевод завершится ошибкой, так как у получателя недостаточно %@ для приема переводов в других токенах."; "common.era.format" = "#%@"; "wallet.total.balance" = "Общий баланс"; -"assets.manage.title" = "Фильтры"; \ No newline at end of file +"assets.manage.title" = "Фильтры"; +"tabbar.dapps.title_2_4_3" = "Браузер"; +"dapps.decoration.subtitle_2_4_3" = "Исследуйте сеть,\nиспользуя ваши аккаунты из Nova Wallet"; +"dapp.decoration.title_2_4_3" = "Добро пожаловать\nв Nova браузер"; +"dapp.list.featured.websites" = "Популярные сайты"; diff --git a/novawalletIntegrationTests/PayoutRewardsServiceTests.swift b/novawalletIntegrationTests/PayoutRewardsServiceTests.swift index 02173cb7a6..5af294e952 100644 --- a/novawalletIntegrationTests/PayoutRewardsServiceTests.swift +++ b/novawalletIntegrationTests/PayoutRewardsServiceTests.swift @@ -9,8 +9,8 @@ class PayoutRewardsServiceTests: XCTestCase { func testPayoutRewardsListForNominator() throws { let operationManager = OperationManagerFacade.sharedManager - let selectedAccount = "FyE2tgkaAhARtpTJSy8TtJum1PwNHP1nCy3SuFjEGSvNfMv" - let chainId = Chain.kusama.genesisHash + let selectedAccount = "5E5CFCa1p5e14UPYWWAddMeu64ykLxppx37iwrKwuyXj5BjH" + let chainId = Chain.westend.genesisHash let storageFacade = SubstrateStorageTestFacade() let chainRegistry = ChainRegistryFacade.setupForIntegrationTest(with: storageFacade) diff --git a/novawalletTests/Common/Services/ChainRegistry/RuntimeProviderTests.swift b/novawalletTests/Common/Services/ChainRegistry/RuntimeProviderTests.swift index b6d23a322f..6a677549b4 100644 --- a/novawalletTests/Common/Services/ChainRegistry/RuntimeProviderTests.swift +++ b/novawalletTests/Common/Services/ChainRegistry/RuntimeProviderTests.swift @@ -326,6 +326,37 @@ class RuntimeProviderTests: XCTestCase { XCTAssertNoThrow(try fetchCoderFactoryOperation.extractNoCancellableResultData()) } + func testCanCancelSnapshotRequest() throws { + // given + + let runtimeProvider = performSetup(with: { _ in }, commonTypesFetchClosure: { + return CompoundOperationWrapper.createWithResult(nil) + }, chainTypesFetchClosure: { + return CompoundOperationWrapper.createWithResult(nil) + }, runtimeMetadataClosure: { + nil + }, eventVisitorClosure: nil) + + // when + + runtimeProvider.setup() + + let fetchCoderFactoryOperation = runtimeProvider.fetchCoderFactoryOperation() + + DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + .milliseconds(500)) { + XCTAssertEqual(runtimeProvider.pendingRequests.count, 1) + fetchCoderFactoryOperation.cancel() + } + + // then + + OperationQueue().addOperations([fetchCoderFactoryOperation], waitUntilFinished: false) + + XCTAssertNil(fetchCoderFactoryOperation.result) + + XCTAssertTrue(runtimeProvider.pendingRequests.isEmpty) + } + private func performSetup( with eventHandlingClosure: @escaping (EventProtocol) -> (), commonTypesFetchClosure: @escaping () -> CompoundOperationWrapper,