diff --git a/Podfile b/Podfile index dcec44c231..f4a1b0ea66 100644 --- a/Podfile +++ b/Podfile @@ -4,7 +4,7 @@ platform :ios, '13.0' abstract_target 'novawalletAll' do use_frameworks! - pod 'SubstrateSdk', :git => 'https://github.com/nova-wallet/substrate-sdk-ios.git', :tag => '1.13.0' + pod 'SubstrateSdk', :git => 'https://github.com/nova-wallet/substrate-sdk-ios.git', :tag => '1.14.0' pod 'SwiftLint' pod 'R.swift', :inhibit_warnings => true pod 'SoraKeystore', '~> 1.0.0' @@ -31,7 +31,7 @@ abstract_target 'novawalletAll' do inherit! :search_paths pod 'Cuckoo' - pod 'SubstrateSdk', :git => 'https://github.com/nova-wallet/substrate-sdk-ios.git', :tag => '1.13.0' + pod 'SubstrateSdk', :git => 'https://github.com/nova-wallet/substrate-sdk-ios.git', :tag => '1.14.0' pod 'SoraFoundation', :git => 'https://github.com/ERussel/Foundation-iOS.git', :tag => '1.1.0' pod 'R.swift', :inhibit_warnings => true pod 'FireMock', :inhibit_warnings => true diff --git a/Podfile.lock b/Podfile.lock index c4ad16509c..7581bee044 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -94,7 +94,7 @@ PODS: - SoraUI/Skrull (1.11.1) - Sourcery (1.4.1) - Starscream (4.0.8) - - SubstrateSdk (1.13.0): + - SubstrateSdk (1.14.0): - BigInt (~> 5.0) - IrohaCrypto/ed25519 (~> 0.9.0) - IrohaCrypto/Scrypt (~> 0.9.0) @@ -162,7 +162,7 @@ DEPENDENCIES: - SoraUI (from `https://github.com/ERussel/UIkit-iOS.git`, tag `1.11.1`) - Sourcery (~> 1.4) - Starscream (from `https://github.com/ERussel/Starscream.git`, tag `4.0.8`) - - SubstrateSdk (from `https://github.com/nova-wallet/substrate-sdk-ios.git`, tag `1.13.0`) + - SubstrateSdk (from `https://github.com/nova-wallet/substrate-sdk-ios.git`, tag `1.14.0`) - SVGKit (from `https://github.com/SVGKit/SVGKit.git`, tag `3.0.0`) - SwiftAlgorithms (~> 1.0.0) - SwiftFormat/CLI (~> 0.47.13) @@ -220,7 +220,7 @@ EXTERNAL SOURCES: :tag: 4.0.8 SubstrateSdk: :git: https://github.com/nova-wallet/substrate-sdk-ios.git - :tag: 1.13.0 + :tag: 1.14.0 SVGKit: :git: https://github.com/SVGKit/SVGKit.git :tag: 3.0.0 @@ -251,7 +251,7 @@ CHECKOUT OPTIONS: :tag: 4.0.8 SubstrateSdk: :git: https://github.com/nova-wallet/substrate-sdk-ios.git - :tag: 1.13.0 + :tag: 1.14.0 SVGKit: :git: https://github.com/SVGKit/SVGKit.git :tag: 3.0.0 @@ -288,7 +288,7 @@ SPEC CHECKSUMS: SoraUI: e5ceb2cffe40145e589aa464e2e0a8d054011e0b Sourcery: db66600e8b285c427701821598d07cf3c7e6c476 Starscream: b676ee89781677a2d8d36029a78c970710e2d3eb - SubstrateSdk: aad6725e00295219ee9722b1975175ef4db3f4a6 + SubstrateSdk: 1cb78eac5b05f2c259487f3027c3ae807f24c097 SVGKit: 132b010efbf57ec345309fe4a7f627c0a40c5d63 SwiftAlgorithms: 38dda4731d19027fdeee1125f973111bf3386b53 SwiftFormat: 73573b89257437c550b03d934889725fbf8f75e5 @@ -303,6 +303,6 @@ SPEC CHECKSUMS: ZMarkupParser: a92d31ba40695b790f1da5fec98c3d4505341aff ZNSTextAttachment: 4a9b4e8ee1ed087fc893ae6657dfb678f1a00340 -PODFILE CHECKSUM: 9da63967acbaf0ea90ab5cf6e0cef78c59d3668f +PODFILE CHECKSUM: f37e3724d47617fb7ce7ed5e0a583491617b5899 -COCOAPODS: 1.12.1 +COCOAPODS: 1.13.0 diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 2ac49fc6f5..6e119aa64f 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -192,6 +192,9 @@ 0C962F8A2AA8614500C0B551 /* TransactionHistoryLocalFilterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C962F892AA8614500C0B551 /* TransactionHistoryLocalFilterFactory.swift */; }; 0C9680F12A8A85BB006A411B /* TokenAddValidationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9680F02A8A85BB006A411B /* TokenAddValidationFactory.swift */; }; 0C9680F32A8AC2F2006A411B /* EvmTokenAddResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9680F22A8AC2F2006A411B /* EvmTokenAddResult.swift */; }; + 0C9951CA2AE2ABA400B65615 /* AssetListBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9951C92AE2ABA400B65615 /* AssetListBannerCell.swift */; }; + 0C9951CF2AE2BAE500B65615 /* PromotionBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9951CE2AE2BAE500B65615 /* PromotionBannerView.swift */; }; + 0C9951D32AE2DB0200B65615 /* PromotionViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9951D22AE2DB0200B65615 /* PromotionViewModelFactory.swift */; }; 0C9C642D2A8CE30A004DC078 /* SystemAccountValidating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9C642C2A8CE30A004DC078 /* SystemAccountValidating.swift */; }; 0C9C64302A8D6779004DC078 /* StakingNPoolsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9C642F2A8D6779004DC078 /* StakingNPoolsPresenter.swift */; }; 0C9C64322A8D67A0004DC078 /* StakingNPoolsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9C64312A8D67A0004DC078 /* StakingNPoolsInteractor.swift */; }; @@ -649,6 +652,7 @@ 766FE2FAB8509BF0F56EA3C0 /* ParaStkCollatorInfoProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F3B8502E5BF8CDD7ACE2DD0 /* ParaStkCollatorInfoProtocols.swift */; }; 76B0B7147181747A7CEDDDF6 /* GovernanceUnavailableTracksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E25CF67173500E0AC19387 /* GovernanceUnavailableTracksViewController.swift */; }; 76CF8508C6936FC9941F3C3E /* TokensManageAddProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = C995D640129977CAB05982EC /* TokensManageAddProtocols.swift */; }; + 770955712AC722A800A2D388 /* StakingBaseDataValidatingFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770955702AC722A800A2D388 /* StakingBaseDataValidatingFactory.swift */; }; 770F57882A8A2CE0005FD7C1 /* StakingSelectPoolViewStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770F57872A8A2CE0005FD7C1 /* StakingSelectPoolViewStyles.swift */; }; 770F578B2A8A48FF005FD7C1 /* ButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770F578A2A8A48FF005FD7C1 /* ButtonViewModel.swift */; }; 77171CA82A98BBA10032B387 /* NominationPoolErrorPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77171CA72A98BBA10032B387 /* NominationPoolErrorPresentable.swift */; }; @@ -656,11 +660,16 @@ 77204EA42A1CD59100BBDE4A /* GenericBorderedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77204EA32A1CD59100BBDE4A /* GenericBorderedView.swift */; }; 77204EA62A1E0EAA00BBDE4A /* WalletConnectionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77204EA52A1E0EAA00BBDE4A /* WalletConnectionsView.swift */; }; 7725062C2A1C99DB00E653DB /* ReferendumSearchViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7725062B2A1C99DB00E653DB /* ReferendumSearchViewLayout.swift */; }; + 772540342AC41FF7002B3FD4 /* BanxaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772540332AC41FF7002B3FD4 /* BanxaProvider.swift */; }; + 772540362AC45CDB002B3FD4 /* PurchaseFlowManaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772540352AC45CDB002B3FD4 /* PurchaseFlowManaging.swift */; }; + 772540382AC45D22002B3FD4 /* PurchasePresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772540372AC45D22002B3FD4 /* PurchasePresentable.swift */; }; 7726CD552A9728D700CE9064 /* StakingTypeSelectedStakingViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7726CD542A9728D700CE9064 /* StakingTypeSelectedStakingViewModelFactory.swift */; }; 7728E58B2A123AEE007901E0 /* ReferendumsSearchOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7728E58A2A123AEE007901E0 /* ReferendumsSearchOperationFactory.swift */; }; 7728E58D2A123B42007901E0 /* SearchOperationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7728E58C2A123B42007901E0 /* SearchOperationFactory.swift */; }; 7728E58F2A123B70007901E0 /* ReferendumsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7728E58E2A123B70007901E0 /* ReferendumsState.swift */; }; 7728E5912A1324A2007901E0 /* ReferendumsSearchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7728E5902A1324A2007901E0 /* ReferendumsSearchManager.swift */; }; + 772AD8E62AC5A87200B0C41A /* MinStakeCrossedParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772AD8E52AC5A87200B0C41A /* MinStakeCrossedParams.swift */; }; + 772AD8E82AC5E30000B0C41A /* StakingBaseErrorPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772AD8E72AC5E30000B0C41A /* StakingBaseErrorPresentable.swift */; }; 772B1C7D2A93837800A19061 /* StartStakingCustomValidatorListWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772B1C7C2A93837800A19061 /* StartStakingCustomValidatorListWireframe.swift */; }; 7731E9C42A14DA3F0085B5FF /* BorderedActionControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7731E9C32A14DA3F0085B5FF /* BorderedActionControlView.swift */; }; 7738FB6A2A4C5D1A00797439 /* StartStakingRelaychainInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7738FB692A4C5D1A00797439 /* StartStakingRelaychainInteractor.swift */; }; @@ -3690,7 +3699,6 @@ D9746967761B1B4772A562B2 /* TokenManageSingleViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF5B66ACB3DFA32DD43BD75 /* TokenManageSingleViewLayout.swift */; }; D9D163642744F60D00681C1F /* ExternalContribution.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D163632744F60D00681C1F /* ExternalContribution.swift */; }; D9D163662744F9BF00681C1F /* ExternalContributionSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D163652744F9BF00681C1F /* ExternalContributionSource.swift */; }; - D9D1636827451C2400681C1F /* AcalaLiquidContributionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D1636727451C2400681C1F /* AcalaLiquidContributionResponse.swift */; }; D9ECCCCF1449EFAFD0FA886E /* ParaStkStakeSetupViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DDD9500E4A1040D863BC1E /* ParaStkStakeSetupViewLayout.swift */; }; DA53192887DFEEE42B658286 /* TokensManageAddViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A5632D4768BC5039EE8276 /* TokensManageAddViewLayout.swift */; }; DAB29F2A9C864D7FCF1AF934 /* UsernameSetupProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD098B40697FD6CC08F9A6AC /* UsernameSetupProtocols.swift */; }; @@ -4153,6 +4161,9 @@ 0C962F892AA8614500C0B551 /* TransactionHistoryLocalFilterFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryLocalFilterFactory.swift; sourceTree = ""; }; 0C9680F02A8A85BB006A411B /* TokenAddValidationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenAddValidationFactory.swift; sourceTree = ""; }; 0C9680F22A8AC2F2006A411B /* EvmTokenAddResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvmTokenAddResult.swift; sourceTree = ""; }; + 0C9951C92AE2ABA400B65615 /* AssetListBannerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetListBannerCell.swift; sourceTree = ""; }; + 0C9951CE2AE2BAE500B65615 /* PromotionBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionBannerView.swift; sourceTree = ""; }; + 0C9951D22AE2DB0200B65615 /* PromotionViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionViewModelFactory.swift; sourceTree = ""; }; 0C9C642C2A8CE30A004DC078 /* SystemAccountValidating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemAccountValidating.swift; sourceTree = ""; }; 0C9C642F2A8D6779004DC078 /* StakingNPoolsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingNPoolsPresenter.swift; sourceTree = ""; }; 0C9C64312A8D67A0004DC078 /* StakingNPoolsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingNPoolsInteractor.swift; sourceTree = ""; }; @@ -4608,6 +4619,7 @@ 75ADB95DAB1F29E6A3FDD166 /* WalletConnectSessionDetailsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsPresenter.swift; sourceTree = ""; }; 75CFAA1D2D04553B10421C69 /* DAppAuthConfirmProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DAppAuthConfirmProtocols.swift; sourceTree = ""; }; 76AA6A6232B1CF2D5AF74D0D /* ParaStkUnstakeInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ParaStkUnstakeInteractor.swift; sourceTree = ""; }; + 770955702AC722A800A2D388 /* StakingBaseDataValidatingFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingBaseDataValidatingFactory.swift; sourceTree = ""; }; 770F57872A8A2CE0005FD7C1 /* StakingSelectPoolViewStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingSelectPoolViewStyles.swift; sourceTree = ""; }; 770F578A2A8A48FF005FD7C1 /* ButtonViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonViewModel.swift; sourceTree = ""; }; 77171CA72A98BBA10032B387 /* NominationPoolErrorPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NominationPoolErrorPresentable.swift; sourceTree = ""; }; @@ -4615,12 +4627,17 @@ 77204EA32A1CD59100BBDE4A /* GenericBorderedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericBorderedView.swift; sourceTree = ""; }; 77204EA52A1E0EAA00BBDE4A /* WalletConnectionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectionsView.swift; sourceTree = ""; }; 7725062B2A1C99DB00E653DB /* ReferendumSearchViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReferendumSearchViewLayout.swift; sourceTree = ""; }; + 772540332AC41FF7002B3FD4 /* BanxaProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BanxaProvider.swift; sourceTree = ""; }; + 772540352AC45CDB002B3FD4 /* PurchaseFlowManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseFlowManaging.swift; sourceTree = ""; }; + 772540372AC45D22002B3FD4 /* PurchasePresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchasePresentable.swift; sourceTree = ""; }; 7726CD542A9728D700CE9064 /* StakingTypeSelectedStakingViewModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingTypeSelectedStakingViewModelFactory.swift; sourceTree = ""; }; 7728E58A2A123AEE007901E0 /* ReferendumsSearchOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumsSearchOperationFactory.swift; sourceTree = ""; }; 7728E58C2A123B42007901E0 /* SearchOperationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOperationFactory.swift; sourceTree = ""; }; 7728E58E2A123B70007901E0 /* ReferendumsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumsState.swift; sourceTree = ""; }; 7728E5902A1324A2007901E0 /* ReferendumsSearchManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferendumsSearchManager.swift; sourceTree = ""; }; 7728E5922A13290D007901E0 /* GenericLens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericLens.swift; sourceTree = ""; }; + 772AD8E52AC5A87200B0C41A /* MinStakeCrossedParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinStakeCrossedParams.swift; sourceTree = ""; }; + 772AD8E72AC5E30000B0C41A /* StakingBaseErrorPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakingBaseErrorPresentable.swift; sourceTree = ""; }; 772B1C7C2A93837800A19061 /* StartStakingCustomValidatorListWireframe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StartStakingCustomValidatorListWireframe.swift; sourceTree = ""; }; 7731E9C32A14DA3F0085B5FF /* BorderedActionControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorderedActionControlView.swift; sourceTree = ""; }; 7738FB692A4C5D1A00797439 /* StartStakingRelaychainInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartStakingRelaychainInteractor.swift; sourceTree = ""; }; @@ -7690,7 +7707,6 @@ D9046DBB27453D5C00C29F2E /* ParallelContributionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParallelContributionResponse.swift; sourceTree = ""; }; D9D163632744F60D00681C1F /* ExternalContribution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalContribution.swift; sourceTree = ""; }; D9D163652744F9BF00681C1F /* ExternalContributionSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalContributionSource.swift; sourceTree = ""; }; - D9D1636727451C2400681C1F /* AcalaLiquidContributionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcalaLiquidContributionResponse.swift; sourceTree = ""; }; D9DFFBEFB45E0A03FAA142C3 /* ReferendumsFiltersViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ReferendumsFiltersViewController.swift; sourceTree = ""; }; D9E599C8CB87A955659DAEBF /* NPoolsUnstakeSetupProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NPoolsUnstakeSetupProtocols.swift; sourceTree = ""; }; DA086DFCCA0976489FD38B95 /* MoonbeamTermsInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MoonbeamTermsInteractor.swift; sourceTree = ""; }; @@ -8408,6 +8424,14 @@ path = LocalFilter; sourceTree = ""; }; + 0C9951CD2AE2BAA700B65615 /* PromotionBannerView */ = { + isa = PBXGroup; + children = ( + 0C9951CE2AE2BAE500B65615 /* PromotionBannerView.swift */, + ); + path = PromotionBannerView; + sourceTree = ""; + }; 0C9C642B2A8CE2D4004DC078 /* SystemAccounts */ = { isa = PBXGroup; children = ( @@ -8447,6 +8471,8 @@ F4CFBAC4468FAD5728719A7D /* ClaimRewards */, 0CB261D62A9893BD00287305 /* Unstake */, D1AABAFE54CA7C8321264E64 /* BondMore */, + ADE9FF1DB92333FA89C7F683 /* Selection */, + F3286B3F0CD8E7D358AC93EA /* Search */, ); path = NominationPools; sourceTree = ""; @@ -10656,6 +10682,7 @@ children = ( 8448336627FAAF780077FB55 /* TransakProvider.swift */, 888B8E94291D274B00AA78E9 /* MercuryoProvider.swift */, + 772540332AC41FF7002B3FD4 /* BanxaProvider.swift */, 8436EDE625895846004D9E97 /* PurchaseProvider.swift */, 8436EDEE25896722004D9E97 /* PurchaseAggregator+Default.swift */, 8488ECD6258CDCBC004591CC /* PurchaseCompletionHandler.swift */, @@ -12977,6 +13004,8 @@ 8846F71F29D56A0700B8B776 /* Web3NameAddressListPresentable.swift */, 844C3E762A09228200C4305F /* WalletChoosePresentable.swift */, 0CC2E5692A6E6EBB004092E7 /* LocalStorageProviderObserving.swift */, + 772540352AC45CDB002B3FD4 /* PurchaseFlowManaging.swift */, + 772540372AC45D22002B3FD4 /* PurchasePresentable.swift */, ); path = Protocols; sourceTree = ""; @@ -13241,6 +13270,7 @@ 8490149D24AA7F9A008F705E /* View */ = { isa = PBXGroup; children = ( + 0C9951CD2AE2BAA700B65615 /* PromotionBannerView */, 0C79C8932A7BDED700B171E3 /* TitleHorizontalMultivalueView */, 84F96FA32A136E0400EB70E2 /* GladingView */, 84B24FA12A2F1C6C00F9BF59 /* CollectionView */, @@ -13520,6 +13550,7 @@ 0C13380F2AB832B30036BCD6 /* QRImageViewModel.swift */, 0C1338112AB8330D0036BCD6 /* AnimatedImageViewModel.swift */, 0C1338132AB834750036BCD6 /* QRImageViewModelFactory.swift */, + 0C9951D22AE2DB0200B65615 /* PromotionViewModelFactory.swift */, ); path = ViewModel; sourceTree = ""; @@ -15064,6 +15095,7 @@ 84C7DA5225EE2DD900F8C318 /* Protocols */ = { isa = PBXGroup; children = ( + 772AD8E72AC5E30000B0C41A /* StakingBaseErrorPresentable.swift */, 84C7DA5325EE2DF000F8C318 /* StakingErrorPresentable.swift */, 84350AD3284580F50031EF24 /* StakingTotalStakePresentable.swift */, 84350AD52845836C0031EF24 /* IdentityPresentable.swift */, @@ -15557,14 +15589,16 @@ 84DBEA4B265ED4F100FDF73C /* Validation */ = { isa = PBXGroup; children = ( + 770955702AC722A800A2D388 /* StakingBaseDataValidatingFactory.swift */, 84DD5F7B263DFEC600425ACF /* StakingDataValidatorFactory.swift */, + 77171CA92A98BC420032B387 /* NominationPoolDataValidatorFactory.swift */, 848077CF2837C637003B7C79 /* ParachainStakingValidatorFactory.swift */, 848077D12837CAE5003B7C79 /* ParachainStakingErrorPresentable.swift */, 8460E717284CF124002896E9 /* ParachainStakingValidatorFactoryProtocol.swift */, 8460E719284D3AF5002896E9 /* ParaStkMinDelegationParams.swift */, 0C13D3102A80CA9A0054BB6F /* StakingDataValidatorFactory+Plank.swift */, - 77171CA92A98BC420032B387 /* NominationPoolDataValidatorFactory.swift */, 0C12A2482AA35AE300C7FA49 /* RelaychainStakingValidatorFacade.swift */, + 772AD8E52AC5A87200B0C41A /* MinStakeCrossedParams.swift */, ); path = Validation; sourceTree = ""; @@ -15695,6 +15729,7 @@ 843E9B2727C83985009C143A /* AssetListNftsCell.swift */, 88DC3E26292CABCB00DBCE4D /* AssetListStyles.swift */, 0C9ECB592A4A9AB400BDCA73 /* AssetListAccountCell.swift */, + 0C9951C92AE2ABA400B65615 /* AssetListBannerCell.swift */, ); path = View; sourceTree = ""; @@ -16016,9 +16051,7 @@ CCD822B8B9ED8FF81C834DD5 /* StartStakingInfo */, 7066B343B912F72345D541F2 /* StakingSetupAmount */, 17432B4B5D8D9DC5C22CA238 /* StakingType */, - ADE9FF1DB92333FA89C7F683 /* StakingSelectPool */, 92CDAD21CEED554306CAF5D8 /* StartStakingConfirm */, - F3286B3F0CD8E7D358AC93EA /* NominationPoolSearch */, ); path = Staking; sourceTree = ""; @@ -17141,7 +17174,7 @@ path = AddToken; sourceTree = ""; }; - ADE9FF1DB92333FA89C7F683 /* StakingSelectPool */ = { + ADE9FF1DB92333FA89C7F683 /* Selection */ = { isa = PBXGroup; children = ( 770F57892A8A48F7005FD7C1 /* Model */, @@ -17153,7 +17186,7 @@ E7F38FDB907F69A26B93B4E6 /* StakingSelectPoolViewController.swift */, 4E553E3D45A7A759C917A4B2 /* StakingSelectPoolViewFactory.swift */, ); - path = StakingSelectPool; + path = Selection; sourceTree = ""; }; ADEC075C60AA6D00785D2BDF /* AssetList */ = { @@ -17910,7 +17943,6 @@ D9046DB927451ED700C29F2E /* ParallelContributionSource.swift */, D9046DBB27453D5C00C29F2E /* ParallelContributionResponse.swift */, 8459A9C727469E4B000D6278 /* AcalaContributionSource.swift */, - D9D1636727451C2400681C1F /* AcalaLiquidContributionResponse.swift */, ); path = ExternalContibution; sourceTree = ""; @@ -18144,7 +18176,7 @@ path = Purchase; sourceTree = ""; }; - F3286B3F0CD8E7D358AC93EA /* NominationPoolSearch */ = { + F3286B3F0CD8E7D358AC93EA /* Search */ = { isa = PBXGroup; children = ( A31B8F292DA050D1D19B9F5F /* NominationPoolSearchProtocols.swift */, @@ -18157,7 +18189,7 @@ 77895C9E2A8F5D40006870FB /* NominationPoolSearchManager.swift */, 77895CA02A8F7360006870FB /* NominationPoolSearchOperationFactory.swift */, ); - path = NominationPoolSearch; + path = Search; sourceTree = ""; }; F3E50DB617DFDE30FE6FA185 /* ParaStkUnstake */ = { @@ -19078,7 +19110,6 @@ 0C626D1B2A8F519100CDAF4E /* StakingMainPresenterFactory+NominationPools.swift in Sources */, 846449FF2567090B004EAA4B /* TriangularedView+Inspectable.swift in Sources */, 8460E11D2542011200826F55 /* DetailsTriangularedView.swift in Sources */, - D9D1636827451C2400681C1F /* AcalaLiquidContributionResponse.swift in Sources */, 840D92A9278EEE5C0007B979 /* DAppOperationBaseInteractor.swift in Sources */, 841AAC2326F6879900F0A25E /* AssetBalanceDisplayInfo.swift in Sources */, 8842105A289BBA8D00306F2C /* CurrencyWireframe.swift in Sources */, @@ -19632,6 +19663,7 @@ 8858917229A4B55600320896 /* DelegateInfoView.swift in Sources */, 8489A6DC27FE9F570040C066 /* StakingUnbondingItemViewModel.swift in Sources */, 848DE76626D642670045CD29 /* StorageMigrator.swift in Sources */, + 0C9951CF2AE2BAE500B65615 /* PromotionBannerView.swift in Sources */, F4AE12A1268DD69B0097D1C7 /* MnemonicTextNormalizer.swift in Sources */, 0C13D31A2A8222D20054BB6F /* StartStakingConfirmInteractorError.swift in Sources */, 8463A72525E3A82A003B8160 /* DataProviderProxyTrigger.swift in Sources */, @@ -20300,6 +20332,7 @@ 84EBFCE7285E7A7C0006327E /* XcmMessage.swift in Sources */, 8424DB0B26B8466A008C834F /* ValidatorOperationFactoryProtocol.swift in Sources */, 848FFE6625E6742400652AA5 /* EraValidatorService.swift in Sources */, + 772AD8E82AC5E30000B0C41A /* StakingBaseErrorPresentable.swift in Sources */, 8493D3E927059B6700157009 /* StakingServiceFactory.swift in Sources */, 887C44FB29AC7B8700950F98 /* DelegateSingleVoteHeader.swift in Sources */, 84729758260AA519009B86D0 /* SelectValidatorsConfirmInteractorBase.swift in Sources */, @@ -20617,6 +20650,7 @@ 8489A6E227FEFC730040C066 /* StakingRebondOption.swift in Sources */, 84CFF1E626526FBC00DB7CF7 /* StakingBondMoreInteractor.swift in Sources */, 844D2A44281B28FB0049CF5E /* StackTitleMultiValueCell.swift in Sources */, + 0C9951CA2AE2ABA400B65615 /* AssetListBannerCell.swift in Sources */, 84E172CF28BE468D00DC85B6 /* MessageSheetPresentable.swift in Sources */, 8487583727F06AF300495306 /* AddressScanDelegate.swift in Sources */, 3E480EEAF501AEB5D543506D /* UsernameSetupPresenter.swift in Sources */, @@ -20909,6 +20943,7 @@ 843E9B3D27C8E8F1009C143A /* NftMediaView.swift in Sources */, 844D22A325EE7E4600C022F7 /* AnySingleValueProvider.swift in Sources */, 8440F4A22959B63300CAFBF9 /* SecuredApplicationHandlerProxy.swift in Sources */, + 772540342AC41FF7002B3FD4 /* BanxaProvider.swift in Sources */, 2AC7BC8B273435CE001D99B0 /* BottomSheetInfoTableCell.swift in Sources */, 844304622A28C9FA00DE36DE /* MultistakingSyncState.swift in Sources */, 0C626D1F2A92AA0F00CDAF4E /* NominationPoolsDataProviding.swift in Sources */, @@ -21219,6 +21254,7 @@ 8428229A289BC8E400163031 /* AddAccount+ParitySignerWelcomeWireframe.swift in Sources */, 8471577D2910F18300D7D003 /* GovernanceUnlockProtocols.swift in Sources */, 2CF2F93AF862CF54FC46B560 /* PurchaseInteractor.swift in Sources */, + 0C9951D32AE2DB0200B65615 /* PromotionViewModelFactory.swift in Sources */, 77EFFC912A7276F1009E28F8 /* StakingTypeAccountView.swift in Sources */, 90EFE3768F1375470FDBE6F6 /* PurchaseViewFactory.swift in Sources */, F452D895273D22CF008F7295 /* SettingsTableHeaderView.swift in Sources */, @@ -21619,6 +21655,7 @@ 77F9FB072A9D96E900820625 /* NominationPoolBondMoreSetupPresenter.swift in Sources */, D1C6EABB48DC3EE254E5A095 /* CrowdloanContributionConfirmPresenter.swift in Sources */, 84BCC71C2924B69D00354DE0 /* ERC20SubscriptionManager.swift in Sources */, + 772540362AC45CDB002B3FD4 /* PurchaseFlowManaging.swift in Sources */, 843A2C7126A85B5B00266F53 /* GenericTitleValueView.swift in Sources */, 847F2D4F27AA695F00AFD476 /* AssetListGroupModel.swift in Sources */, 8499FED827BFCABD00712589 /* NftModel+Identifier.swift in Sources */, @@ -21972,6 +22009,7 @@ 84282290289BB3C700163031 /* ParitySignerWalletOperationFactory.swift in Sources */, 88D02FF029431F8300E26390 /* AssetDetailsViewModelFactory.swift in Sources */, 84CE22DB29A38ACD00A03156 /* GovernanceDelegateInfoPresenter+Protocol.swift in Sources */, + 772AD8E62AC5A87200B0C41A /* MinStakeCrossedParams.swift in Sources */, 8217DCBEB74527D57AC82070 /* ParaStkStakeConfirmViewLayout.swift in Sources */, 06FD6F5999D57B27B29C8738 /* ParaStkStakeConfirmViewFactory.swift in Sources */, 0C9525E32A7AAB2A00BD724D /* StakingTimeModel.swift in Sources */, @@ -22335,6 +22373,7 @@ 84BAD21A293B18EC00C55C49 /* RemoteAssetModel.swift in Sources */, DE52F23521D54A07F558EB1B /* ReferendumVoteConfirmInteractor.swift in Sources */, 844304652A28EB0D00DE36DE /* ObservableSyncService.swift in Sources */, + 770955712AC722A800A2D388 /* StakingBaseDataValidatingFactory.swift in Sources */, B30FEC6F62918BC6F38396A2 /* ReferendumVoteConfirmViewController.swift in Sources */, 840AE2E129C9A24C008FF665 /* EtherscanTxHistoryResponse.swift in Sources */, 0C463FD02A592ACD003E71C9 /* PartialInterpolatingMotionEffect.swift in Sources */, @@ -22666,6 +22705,7 @@ F7D3092FDF42D9356654D85A /* StartStakingConfirmInteractor.swift in Sources */, A99428A540CDA6B359220477 /* StartStakingConfirmViewController.swift in Sources */, DB8AD60FE397F764623A566F /* StartStakingConfirmViewLayout.swift in Sources */, + 772540382AC45D22002B3FD4 /* PurchasePresentable.swift in Sources */, A428E022070EF536D4B0B5EC /* StartStakingConfirmViewFactory.swift in Sources */, AD36A601830C69DA003B3B01 /* StakingSelectPoolProtocols.swift in Sources */, 141BF00B1B59940711773726 /* StakingSelectPoolWireframe.swift in Sources */, diff --git a/novawallet/Assets.xcassets/iconCloseWithBg.imageset/Contents.json b/novawallet/Assets.xcassets/iconCloseWithBg.imageset/Contents.json new file mode 100644 index 0000000000..39a0706618 --- /dev/null +++ b/novawallet/Assets.xcassets/iconCloseWithBg.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "iconCloseWithBg.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/novawallet/Assets.xcassets/iconCloseWithBg.imageset/iconCloseWithBg.pdf b/novawallet/Assets.xcassets/iconCloseWithBg.imageset/iconCloseWithBg.pdf new file mode 100644 index 0000000000..78df52c7bb Binary files /dev/null and b/novawallet/Assets.xcassets/iconCloseWithBg.imageset/iconCloseWithBg.pdf differ diff --git a/novawallet/Assets.xcassets/iconsBuyProviders/Contents.json b/novawallet/Assets.xcassets/iconsBuyProviders/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/novawallet/Assets.xcassets/iconsBuyProviders/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/novawallet/Assets.xcassets/iconsBuyProviders/iconBanxa.imageset/Banxa.pdf b/novawallet/Assets.xcassets/iconsBuyProviders/iconBanxa.imageset/Banxa.pdf new file mode 100644 index 0000000000..4766616055 Binary files /dev/null and b/novawallet/Assets.xcassets/iconsBuyProviders/iconBanxa.imageset/Banxa.pdf differ diff --git a/novawallet/Assets.xcassets/iconsBuyProviders/iconBanxa.imageset/Contents.json b/novawallet/Assets.xcassets/iconsBuyProviders/iconBanxa.imageset/Contents.json new file mode 100644 index 0000000000..3cdcf0d493 --- /dev/null +++ b/novawallet/Assets.xcassets/iconsBuyProviders/iconBanxa.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Banxa.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/novawallet/Assets.xcassets/iconMercuryo.imageset/Contents.json b/novawallet/Assets.xcassets/iconsBuyProviders/iconMercuryo.imageset/Contents.json similarity index 100% rename from novawallet/Assets.xcassets/iconMercuryo.imageset/Contents.json rename to novawallet/Assets.xcassets/iconsBuyProviders/iconMercuryo.imageset/Contents.json diff --git a/novawallet/Assets.xcassets/iconMercuryo.imageset/White Icons.pdf b/novawallet/Assets.xcassets/iconsBuyProviders/iconMercuryo.imageset/White Icons.pdf similarity index 100% rename from novawallet/Assets.xcassets/iconMercuryo.imageset/White Icons.pdf rename to novawallet/Assets.xcassets/iconsBuyProviders/iconMercuryo.imageset/White Icons.pdf diff --git a/novawallet/Assets.xcassets/iconTransak.imageset/Contents.json b/novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/Contents.json similarity index 100% rename from novawallet/Assets.xcassets/iconTransak.imageset/Contents.json rename to novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/Contents.json diff --git a/novawallet/Assets.xcassets/iconTransak.imageset/iconTransak.pdf b/novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/iconTransak.pdf similarity index 100% rename from novawallet/Assets.xcassets/iconTransak.imageset/iconTransak.pdf rename to novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/iconTransak.pdf diff --git a/novawallet/Assets.xcassets/imagePolkadotStakingBg.imageset/Contents.json b/novawallet/Assets.xcassets/imagePolkadotStakingBg.imageset/Contents.json new file mode 100644 index 0000000000..7582f4bedc --- /dev/null +++ b/novawallet/Assets.xcassets/imagePolkadotStakingBg.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "imagePolkadotStakingBg.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/novawallet/Assets.xcassets/imagePolkadotStakingBg.imageset/imagePolkadotStakingBg.pdf b/novawallet/Assets.xcassets/imagePolkadotStakingBg.imageset/imagePolkadotStakingBg.pdf new file mode 100644 index 0000000000..79327b3e11 Binary files /dev/null and b/novawallet/Assets.xcassets/imagePolkadotStakingBg.imageset/imagePolkadotStakingBg.pdf differ diff --git a/novawallet/Assets.xcassets/imagePolkadotStakingPromo.imageset/Contents.json b/novawallet/Assets.xcassets/imagePolkadotStakingPromo.imageset/Contents.json new file mode 100644 index 0000000000..09dda01caa --- /dev/null +++ b/novawallet/Assets.xcassets/imagePolkadotStakingPromo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "imagePolkadotStakingPromo.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/novawallet/Assets.xcassets/imagePolkadotStakingPromo.imageset/imagePolkadotStakingPromo.pdf b/novawallet/Assets.xcassets/imagePolkadotStakingPromo.imageset/imagePolkadotStakingPromo.pdf new file mode 100644 index 0000000000..44c6111ff5 Binary files /dev/null and b/novawallet/Assets.xcassets/imagePolkadotStakingPromo.imageset/imagePolkadotStakingPromo.pdf differ diff --git a/novawallet/Common/Configs/ApplicationConfigs.swift b/novawallet/Common/Configs/ApplicationConfigs.swift index f32f97a355..3e3b6bdb71 100644 --- a/novawallet/Common/Configs/ApplicationConfigs.swift +++ b/novawallet/Common/Configs/ApplicationConfigs.swift @@ -129,9 +129,9 @@ extension ApplicationConfig: ApplicationConfigProtocol { var chainListURL: URL { #if F_RELEASE - URL(string: "https://raw.githubusercontent.com/novasamatech/nova-utils/master/chains/v14/chains.json")! + URL(string: "https://raw.githubusercontent.com/novasamatech/nova-utils/master/chains/v15/chains.json")! #else - URL(string: "https://raw.githubusercontent.com/novasamatech/nova-utils/master/chains/v14/chains_dev.json")! + URL(string: "https://raw.githubusercontent.com/novasamatech/nova-utils/master/chains/v15/chains_dev.json")! #endif } diff --git a/novawallet/Common/Extension/SettingsExtension.swift b/novawallet/Common/Extension/SettingsExtension.swift index 0f04d31ac1..de79626ccc 100644 --- a/novawallet/Common/Extension/SettingsExtension.swift +++ b/novawallet/Common/Extension/SettingsExtension.swift @@ -15,6 +15,7 @@ enum SettingsKey: String { case skippedUpdateVersion case skippedAddDelegationTracksHint case pinConfirmationEnabled + case polkadotStakingPromoSeen } extension SettingsManagerProtocol { @@ -162,4 +163,14 @@ extension SettingsManagerProtocol { set(value: newValue, for: SettingsKey.skippedAddDelegationTracksHint.rawValue) } } + + var polkadotStakingPromoSeen: Bool { + get { + bool(for: SettingsKey.polkadotStakingPromoSeen.rawValue) ?? false + } + + set { + set(value: newValue, for: SettingsKey.polkadotStakingPromoSeen.rawValue) + } + } } diff --git a/novawallet/Common/Extension/UIKit/RoundedButton+Styles.swift b/novawallet/Common/Extension/UIKit/RoundedButton+Styles.swift index 0a7c2c7f2a..2c4b4732d3 100644 --- a/novawallet/Common/Extension/UIKit/RoundedButton+Styles.swift +++ b/novawallet/Common/Extension/UIKit/RoundedButton+Styles.swift @@ -25,6 +25,16 @@ extension RoundedButton { changesContentOpacityWhenHighlighted = true } + func applyIconWithBackgroundStyle() { + roundedBackgroundView?.shadowOpacity = 0 + roundedBackgroundView?.fillColor = R.color.colorBlockBackground()! + roundedBackgroundView?.highlightedFillColor = R.color.colorBlockBackground()! + roundedBackgroundView?.strokeColor = .clear + roundedBackgroundView?.highlightedStrokeColor = .clear + roundedBackgroundView?.cornerRadius = 12 + changesContentOpacityWhenHighlighted = true + } + func applySecondaryStyle() { roundedBackgroundView?.shadowOpacity = 0.0 roundedBackgroundView?.fillColor = R.color.colorButtonBackgroundSecondary()! diff --git a/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift b/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift index 0de735147e..f5f33d2b16 100644 --- a/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift +++ b/novawallet/Common/Extension/UIKit/Style/UILabel+Style.swift @@ -36,6 +36,11 @@ extension UILabel.Style { font: .semiBoldSubheadline ) + static let semiboldSubhedlinePrimary = UILabel.Style( + textColor: R.color.colorTextPrimary(), + font: .semiBoldSubheadline + ) + static let semiboldBodyPrimary = UILabel.Style( textColor: R.color.colorTextPrimary(), font: .semiBoldBody @@ -81,6 +86,11 @@ extension UILabel.Style { font: .regularFootnote ) + static let caption1Primary = UILabel.Style( + textColor: R.color.colorTextPrimary(), + font: .caption1 + ) + static let caption1Secondary = UILabel.Style( textColor: R.color.colorTextSecondary(), font: .caption1 diff --git a/novawallet/Common/Helpers/KeyboardAppearanceState.swift b/novawallet/Common/Helpers/KeyboardAppearanceState.swift index 3368731283..e2d17e48ec 100644 --- a/novawallet/Common/Helpers/KeyboardAppearanceState.swift +++ b/novawallet/Common/Helpers/KeyboardAppearanceState.swift @@ -3,6 +3,11 @@ import UIKit protocol KeyboardAppearanceStrategyProtocol { func onViewWillAppear(for target: UIView) func onViewDidAppear(for target: UIView) + func onCellSelected(for target: UIView) +} + +extension KeyboardAppearanceStrategyProtocol { + func onCellSelected(for _: UIView) {} } final class EventDrivenKeyboardStrategy: KeyboardAppearanceStrategyProtocol { @@ -56,4 +61,10 @@ final class ModalNavigationKeyboardStrategy: KeyboardAppearanceStrategyProtocol isPresented = true } } + + func onCellSelected(for target: UIView) { + if isPresented { + target.resignFirstResponder() + } + } } diff --git a/novawallet/Common/Model/ChainRegistry/RemoteChain/RemoteAssetModel+Evm.swift b/novawallet/Common/Model/ChainRegistry/RemoteChain/RemoteAssetModel+Evm.swift index b93ae61288..c47034f894 100644 --- a/novawallet/Common/Model/ChainRegistry/RemoteChain/RemoteAssetModel+Evm.swift +++ b/novawallet/Common/Model/ChainRegistry/RemoteChain/RemoteAssetModel+Evm.swift @@ -25,7 +25,7 @@ extension RemoteAssetModel { staking: nil, type: AssetType.evmAsset.rawValue, typeExtras: JSON.stringValue(evmInstance.contractAddress), - buyProviders: nil + buyProviders: evmInstance.buyProviders ) } } diff --git a/novawallet/Common/Model/ChainRegistry/RemoteChain/RemoteEvmToken.swift b/novawallet/Common/Model/ChainRegistry/RemoteChain/RemoteEvmToken.swift index ac8bc26d24..5abf690d6a 100644 --- a/novawallet/Common/Model/ChainRegistry/RemoteChain/RemoteEvmToken.swift +++ b/novawallet/Common/Model/ChainRegistry/RemoteChain/RemoteEvmToken.swift @@ -1,4 +1,5 @@ import Foundation +import SubstrateSdk struct RemoteEvmToken: Codable { let symbol: String @@ -11,5 +12,6 @@ struct RemoteEvmToken: Codable { struct Instance: Codable { let chainId: String let contractAddress: String + let buyProviders: JSON? } } diff --git a/novawallet/Common/Protocols/PurchaseFlowManaging.swift b/novawallet/Common/Protocols/PurchaseFlowManaging.swift new file mode 100644 index 0000000000..35348b3a48 --- /dev/null +++ b/novawallet/Common/Protocols/PurchaseFlowManaging.swift @@ -0,0 +1,68 @@ +import SoraFoundation + +protocol PurchaseFlowManaging { + func startPuchaseFlow( + from view: ControllerBackedProtocol?, + purchaseActions: [PurchaseAction], + wireframe: (PurchasePresentable & AlertPresentable)?, + locale: Locale + ) +} + +extension PurchaseFlowManaging where Self: ModalPickerViewControllerDelegate & PurchaseDelegate { + func startPuchaseFlow( + from view: ControllerBackedProtocol?, + purchaseActions: [PurchaseAction], + wireframe: (PurchasePresentable & AlertPresentable)?, + locale: Locale + ) { + guard !purchaseActions.isEmpty else { + return + } + if purchaseActions.count == 1 { + startPuchaseFlow(from: view, purchaseAction: purchaseActions[0], wireframe: wireframe, locale: locale) + } else { + wireframe?.showPurchaseProviders( + from: view, + actions: purchaseActions, + delegate: self + ) + } + } + + func startPuchaseFlow( + from view: ControllerBackedProtocol?, + purchaseAction: PurchaseAction, + wireframe: (PurchasePresentable & AlertPresentable)?, + locale: Locale + ) { + let title = R.string.localizable.commonAlertExternalLinkDisclaimerTitle(preferredLanguages: locale.rLanguages) + let message = R.string.localizable.commonAlertExternalLinkDisclaimerMessage( + purchaseAction.displayURL, + preferredLanguages: locale.rLanguages + ) + + let closeTitle = R.string.localizable + .commonCancel(preferredLanguages: locale.rLanguages) + let continueTitle = R.string.localizable + .commonContinue(preferredLanguages: locale.rLanguages) + let continueAction = AlertPresentableAction(title: continueTitle) { + wireframe?.showPurchaseTokens( + from: view, + action: purchaseAction, + delegate: self + ) + } + + wireframe?.present( + viewModel: .init( + title: title, + message: message, + actions: [continueAction], + closeAction: closeTitle + ), + style: .alert, + from: view + ) + } +} diff --git a/novawallet/Common/Protocols/PurchasePresentable.swift b/novawallet/Common/Protocols/PurchasePresentable.swift new file mode 100644 index 0000000000..67cc3affbd --- /dev/null +++ b/novawallet/Common/Protocols/PurchasePresentable.swift @@ -0,0 +1,67 @@ +import Foundation + +protocol PurchasePresentable { + func showPurchaseTokens( + from view: ControllerBackedProtocol?, + action: PurchaseAction, + delegate: PurchaseDelegate + ) + + func showPurchaseProviders( + from view: ControllerBackedProtocol?, + actions: [PurchaseAction], + delegate: ModalPickerViewControllerDelegate + ) + + func presentPurchaseDidComplete( + view: ControllerBackedProtocol?, + locale: Locale + ) +} + +extension PurchasePresentable { + func showPurchaseTokens( + from view: ControllerBackedProtocol?, + action: PurchaseAction, + delegate: PurchaseDelegate + ) { + guard let purchaseView = PurchaseViewFactory.createView( + for: action, + delegate: delegate + ) else { + return + } + purchaseView.controller.modalPresentationStyle = .fullScreen + view?.controller.present(purchaseView.controller, animated: true) + } + + func showPurchaseProviders( + from view: ControllerBackedProtocol?, + actions: [PurchaseAction], + delegate: ModalPickerViewControllerDelegate + ) { + guard let pickerView = ModalPickerFactory.createPickerForList( + actions, + delegate: delegate, + context: nil + ) else { + return + } + guard let navigationController = view?.controller.navigationController else { + return + } + navigationController.present(pickerView, animated: true) + } + + func presentPurchaseDidComplete( + view: ControllerBackedProtocol?, + locale: Locale + ) { + let languages = locale.rLanguages + let message = R.string.localizable + .buyCompleted(preferredLanguages: languages) + + let alertController = ModalAlertFactory.createMultilineSuccessAlert(message) + view?.controller.present(alertController, animated: true) + } +} diff --git a/novawallet/Common/PurchaseProvider/BanxaProvider.swift b/novawallet/Common/PurchaseProvider/BanxaProvider.swift new file mode 100644 index 0000000000..4a84078d45 --- /dev/null +++ b/novawallet/Common/PurchaseProvider/BanxaProvider.swift @@ -0,0 +1,68 @@ +import Foundation +import CryptoKit +import SubstrateSdk + +final class BanxaProvider: PurchaseProviderProtocol { + #if F_RELEASE + let host = "https://novawallet.banxa.com" + #else + let host = "https://novawallet.banxa-sandbox.com" + #endif + + private var callbackUrl: URL? + private var colorCode: String? + private let displayURL = "banxa.com" + + func with(callbackUrl: URL) -> Self { + self.callbackUrl = callbackUrl + return self + } + + func buildPurchaseActions(for chainAsset: ChainAsset, accountId: AccountId) -> [PurchaseAction] { + guard + let banxa = chainAsset.asset.buyProviders?.banxa, + let network = banxa.blockchain?.stringValue, + let token = banxa.coinType?.stringValue, + let address = try? accountId.toAddress(using: chainAsset.chain.chainFormat) else { + return [] + } + + guard let callbackUrl = self.callbackUrl, + let url = buildURL( + address: address, + token: token, + network: network, + callbackUrl: callbackUrl + ) else { + return [] + } + + return [ + PurchaseAction( + title: "Banxa", + url: url, + icon: R.image.iconBanxa()!, + displayURL: displayURL + ) + ] + } + + private func buildURL( + address: AccountAddress, + token: String, + network: String, + callbackUrl _: URL + ) -> URL? { + var components = URLComponents(string: host) + + let queryItems = [ + URLQueryItem(name: "coinType", value: token), + URLQueryItem(name: "blockchain", value: network), + URLQueryItem(name: "walletAddress", value: address) + ] + + components?.queryItems = queryItems + + return components?.url + } +} diff --git a/novawallet/Common/PurchaseProvider/MercuryoProvider.swift b/novawallet/Common/PurchaseProvider/MercuryoProvider.swift index 2557dc312c..eb527cdb75 100644 --- a/novawallet/Common/PurchaseProvider/MercuryoProvider.swift +++ b/novawallet/Common/PurchaseProvider/MercuryoProvider.swift @@ -28,6 +28,7 @@ final class MercuryoProvider: PurchaseProviderProtocol { #endif private var callbackUrl: URL? + private let displayURL = "mercuryo.io" func with(callbackUrl: URL) -> Self { self.callbackUrl = callbackUrl @@ -51,7 +52,12 @@ final class MercuryoProvider: PurchaseProviderProtocol { } return [ - PurchaseAction(title: "Mercuryo", url: url, icon: R.image.iconMercuryo()!) + PurchaseAction( + title: "Mercuryo", + url: url, + icon: R.image.iconMercuryo()!, + displayURL: displayURL + ) ] } diff --git a/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift b/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift index c1406fa9de..36415d2190 100644 --- a/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift +++ b/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift @@ -5,8 +5,9 @@ extension PurchaseAggregator { let config: ApplicationConfigProtocol = ApplicationConfig.shared let purchaseProviders: [PurchaseProviderProtocol] = [ - MercuryoProvider(), - TransakProvider() + TransakProvider(), + BanxaProvider(), + MercuryoProvider() ] return PurchaseAggregator(providers: purchaseProviders) .with(appName: config.purchaseAppName) diff --git a/novawallet/Common/PurchaseProvider/PurchaseProviderProtocol.swift b/novawallet/Common/PurchaseProvider/PurchaseProviderProtocol.swift index 1a2752cdb4..0c4cc24180 100644 --- a/novawallet/Common/PurchaseProvider/PurchaseProviderProtocol.swift +++ b/novawallet/Common/PurchaseProvider/PurchaseProviderProtocol.swift @@ -5,6 +5,7 @@ struct PurchaseAction { let title: String let url: URL let icon: UIImage + let displayURL: String } protocol PurchaseProviderProtocol { diff --git a/novawallet/Common/PurchaseProvider/TransakProvider.swift b/novawallet/Common/PurchaseProvider/TransakProvider.swift index 5ed0bffa3c..f99374e5e7 100644 --- a/novawallet/Common/PurchaseProvider/TransakProvider.swift +++ b/novawallet/Common/PurchaseProvider/TransakProvider.swift @@ -10,6 +10,7 @@ final class TransakProvider: PurchaseProviderProtocol { #endif private var callbackUrl: URL? + private let displayURL = "transak.com" func with(callbackUrl: URL) -> Self { self.callbackUrl = callbackUrl @@ -30,7 +31,12 @@ final class TransakProvider: PurchaseProviderProtocol { return [] } - let action = PurchaseAction(title: "Transak", url: url, icon: R.image.iconTransak()!) + let action = PurchaseAction( + title: "Transak", + url: url, + icon: R.image.iconTransak()!, + displayURL: displayURL + ) return [action] } diff --git a/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeSnapshotOperationFactory.swift b/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeSnapshotOperationFactory.swift index 6191512c6b..f46ecf2ac8 100644 --- a/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeSnapshotOperationFactory.swift +++ b/novawallet/Common/Services/ChainRegistry/RuntimeProviderPool/RuntimeSnapshotOperationFactory.swift @@ -57,14 +57,14 @@ final class RuntimeSnapshotFactory { commonTypes, versioningData: chainTypes, runtimeMetadata: metadata, - customExtensions: DefaultExtrinsicExtension.coders + customExtensions: DefaultExtrinsicExtension.getCoders(for: metadata) ) runtimeMetadata = metadata case let .v14(metadata): catalog = try TypeRegistryCatalog.createFromSiDefinition( versioningData: chainTypes, runtimeMetadata: metadata, - customExtensions: DefaultExtrinsicExtension.coders, + customExtensions: DefaultExtrinsicExtension.getCoders(for: metadata), customTypeMapper: CustomSiMappers.all, customNameMapper: ScaleInfoCamelCaseMapper() ) @@ -120,14 +120,14 @@ final class RuntimeSnapshotFactory { catalog = try TypeRegistryCatalog.createFromTypeDefinition( commonTypes, runtimeMetadata: metadata, - customExtensions: DefaultExtrinsicExtension.coders + customExtensions: DefaultExtrinsicExtension.getCoders(for: metadata) ) runtimeMetadata = metadata case let .v14(metadata): catalog = try TypeRegistryCatalog.createFromSiDefinition( versioningData: commonTypes, runtimeMetadata: metadata, - customExtensions: DefaultExtrinsicExtension.coders, + customExtensions: DefaultExtrinsicExtension.getCoders(for: metadata), customTypeMapper: CustomSiMappers.all, customNameMapper: ScaleInfoCamelCaseMapper() ) @@ -182,14 +182,14 @@ final class RuntimeSnapshotFactory { catalog = try TypeRegistryCatalog.createFromTypeDefinition( ownTypes, runtimeMetadata: metadata, - customExtensions: DefaultExtrinsicExtension.coders + customExtensions: DefaultExtrinsicExtension.getCoders(for: metadata) ) runtimeMetadata = metadata case let .v14(metadata): catalog = try TypeRegistryCatalog.createFromSiDefinition( versioningData: ownTypes, runtimeMetadata: metadata, - customExtensions: DefaultExtrinsicExtension.coders, + customExtensions: DefaultExtrinsicExtension.getCoders(for: metadata), customTypeMapper: CustomSiMappers.all, customNameMapper: ScaleInfoCamelCaseMapper() ) diff --git a/novawallet/Common/Substrate/Extension/ExtrinsicExtension.swift b/novawallet/Common/Substrate/Extension/ExtrinsicExtension.swift index c68d98f87e..eef7c70cdc 100644 --- a/novawallet/Common/Substrate/Extension/ExtrinsicExtension.swift +++ b/novawallet/Common/Substrate/Extension/ExtrinsicExtension.swift @@ -8,11 +8,15 @@ enum DefaultExtrinsicExtension { ] } - static var coders: [ExtrinsicExtensionCoder] { - [ + static func getCoders(for metadata: RuntimeMetadataProtocol) -> [ExtrinsicExtensionCoder] { + let extensionName = ChargeAssetTxPayment.name + + let extraType = metadata.getSignedExtensionType(for: extensionName) + + return [ DefaultExtrinsicExtensionCoder( name: ChargeAssetTxPayment.name, - extraType: "pallet_asset_tx_payment.ChargeAssetTxPayment" + extraType: extraType ?? "pallet_asset_tx_payment.ChargeAssetTxPayment" ) ] } diff --git a/novawallet/Common/View/PromotionBannerView/PromotionBannerView.swift b/novawallet/Common/View/PromotionBannerView/PromotionBannerView.swift new file mode 100644 index 0000000000..147bb284ed --- /dev/null +++ b/novawallet/Common/View/PromotionBannerView/PromotionBannerView.swift @@ -0,0 +1,148 @@ +import UIKit +import SoraUI + +protocol PromotionBannerViewDelegate: AnyObject { + func promotionBannerDidRequestClose(view: PromotionBannerView) +} + +final class PromotionBannerView: UIView { + let backgroundView = UIImageView() + + let titleLabel: UILabel = .create { label in + label.apply(style: Constants.titleStyle) + label.numberOfLines = 0 + } + + let detailsLabel: UILabel = .create { label in + label.apply(style: Constants.detailsStyle) + label.numberOfLines = 0 + } + + let iconImageView = UIImageView() + + let closeButton: RoundedButton = .create { button in + button.applyIconStyle() + button.imageWithTitleView?.iconImage = R.image.iconCloseWithBg()! + } + + weak var delegate: PromotionBannerViewDelegate? + + override init(frame: CGRect) { + super.init(frame: frame) + + setupLayout() + configureHandlers() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func configureHandlers() { + closeButton.addTarget( + self, + action: #selector(actionClose), + for: .touchUpInside + ) + } + + @objc func actionClose() { + delegate?.promotionBannerDidRequestClose(view: self) + } + + private func setupLayout() { + addSubview(backgroundView) + + backgroundView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + addSubview(iconImageView) + + iconImageView.snp.makeConstraints { make in + make.trailing.equalToSuperview().inset(Constants.contentInset.right) + make.centerY.equalToSuperview() + } + + let descriptionView = UIView.vStack( + spacing: Constants.descriptionVerticalSpacing, + [titleLabel, detailsLabel] + ) + + addSubview(descriptionView) + + descriptionView.snp.makeConstraints { make in + make.leading.equalToSuperview().inset(Constants.contentInset.left) + make.top.equalToSuperview().inset(Constants.contentInset.top) + make.trailing.lessThanOrEqualTo(iconImageView.snp.leading).offset(-Constants.descriptionHorizontalSpacing) + } + + addSubview(closeButton) + + closeButton.snp.makeConstraints { make in + make.top.equalToSuperview() + make.right.equalToSuperview() + make.width.equalTo(56) + make.height.equalTo(44) + } + } +} + +extension PromotionBannerView { + struct ViewModel { + let background: UIImage + let title: String + let details: String + let icon: UIImage? + } + + func bind(viewModel: ViewModel) { + backgroundView.image = viewModel.background + titleLabel.text = viewModel.title + detailsLabel.text = viewModel.details + iconImageView.image = viewModel.icon + } +} + +extension PromotionBannerView { + enum Constants { + static let descriptionVerticalSpacing: CGFloat = 8 + static let descriptionHorizontalSpacing: CGFloat = 8 + static let contentInset: UIEdgeInsets = .init(top: 12, left: 16, bottom: 16, right: 24) + static let titleStyle = UILabel.Style.semiboldSubhedlinePrimary + static let detailsStyle = UILabel.Style.footnotePrimary + } + + static func estimateHeight(for viewModel: ViewModel, width: CGFloat) -> CGFloat { + let availableWidth: CGFloat + + let contentWidth = width - Constants.contentInset.left - Constants.contentInset.right + + if let icon = viewModel.icon { + availableWidth = contentWidth - Constants.descriptionHorizontalSpacing - icon.size.width + } else { + availableWidth = contentWidth + } + + let titleSize = viewModel.title.boundingRect( + with: CGSize(width: availableWidth, height: .greatestFiniteMagnitude), + options: .usesLineFragmentOrigin, + attributes: [.font: Constants.titleStyle.font], + context: nil + ) + + let descriptionSize = viewModel.details.boundingRect( + with: CGSize(width: availableWidth, height: .greatestFiniteMagnitude), + options: .usesLineFragmentOrigin, + attributes: [.font: Constants.detailsStyle.font], + context: nil + ) + + let descriptionHeight = titleSize.height + descriptionSize.height + Constants.descriptionVerticalSpacing + + let contentHeight = max(descriptionHeight, viewModel.icon?.size.height ?? 0) + + return Constants.contentInset.top + contentHeight + Constants.contentInset.bottom + } +} diff --git a/novawallet/Common/ViewController/SearchController/BaseTableSearchViewLayout.swift b/novawallet/Common/ViewController/SearchController/BaseTableSearchViewLayout.swift index 7feeed1da9..83dbe4aa43 100644 --- a/novawallet/Common/ViewController/SearchController/BaseTableSearchViewLayout.swift +++ b/novawallet/Common/ViewController/SearchController/BaseTableSearchViewLayout.swift @@ -7,21 +7,28 @@ class BaseTableSearchViewLayout: UIView { view.searchBar.textField.autocorrectionType = .no view.searchBar.textField.autocapitalizationType = .none view.cancelButton.isHidden = true - view.cancelButton.contentInsets = .init(top: 0, left: 0, bottom: 0, right: 16) + view.cancelButton.contentInsets = Constants.cancelButtonInsets return view }() var searchField: UITextField { searchView.searchBar.textField } + var cancelButton: RoundedButton { searchView.cancelButton } let tableView: UITableView = { let tableView = UITableView() tableView.tableFooterView = UIView() tableView.rowHeight = UITableView.automaticDimension + tableView.contentInset = Constants.tableViewContentInsets + tableView.separatorStyle = .none return tableView }() - let emptyStateContainer = UIView() + let contentView: UIView = { + let view = UIView() + view.backgroundColor = .clear + return view + }() override init(frame: CGRect) { super.init(frame: frame) @@ -36,36 +43,42 @@ class BaseTableSearchViewLayout: UIView { } func setupLayout() { - addSubview(searchView) - searchView.snp.makeConstraints { make in - make.leading.top.trailing.equalToSuperview() - make.bottom.equalTo(safeAreaLayoutGuide.snp.top).offset(Constants.searchBarHeight) - } - - addSubview(emptyStateContainer) - emptyStateContainer.snp.makeConstraints { make in - make.leading.trailing.bottom.equalTo(safeAreaLayoutGuide) - make.top.equalTo(searchView.snp.bottom) + addSubview(contentView) + contentView.snp.makeConstraints { make in + make.top.edges.equalToSuperview() } addSubview(tableView) tableView.snp.makeConstraints { make in - make.top.equalTo(searchView.snp.bottom) + make.top.equalToSuperview() make.leading.trailing.equalTo(safeAreaLayoutGuide) make.bottom.equalToSuperview() } + + addSubview(searchView) + searchView.snp.makeConstraints { make in + make.leading.top.trailing.equalToSuperview() + make.bottom.equalTo(safeAreaLayoutGuide.snp.top).offset(Constants.searchBarHeight) + } } func setupStyle() { - let color = R.color.colorSecondaryScreenBackground()! + let color = R.color.colorSecondaryScreenBackground() backgroundColor = color tableView.backgroundColor = color - emptyStateContainer.backgroundColor = color + searchView.blurBackgroundView.borderType = .none } } extension BaseTableSearchViewLayout { enum Constants { static let searchBarHeight: CGFloat = 54 + static let tableViewContentInsets = UIEdgeInsets( + top: Constants.searchBarHeight, + left: 0, + bottom: 16, + right: 0 + ) + static let cancelButtonInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16) } } diff --git a/novawallet/Common/ViewModel/PromotionViewModelFactory.swift b/novawallet/Common/ViewModel/PromotionViewModelFactory.swift new file mode 100644 index 0000000000..e63f78affc --- /dev/null +++ b/novawallet/Common/ViewModel/PromotionViewModelFactory.swift @@ -0,0 +1,12 @@ +import Foundation + +enum PromotionViewModelFactory { + static func createPolkadotStakingPromotion(for locale: Locale) -> PromotionBannerView.ViewModel { + .init( + background: R.image.imagePolkadotStakingBg()!, + title: R.string.localizable.polkadotStakingPromotionTitle(preferredLanguages: locale.rLanguages), + details: R.string.localizable.polkadotStakingPromotionMessage(preferredLanguages: locale.rLanguages), + icon: R.image.imagePolkadotStakingPromo()! + ) + } +} diff --git a/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift b/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift index de7d989fe2..a3042f1a2c 100644 --- a/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift +++ b/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift @@ -3,7 +3,7 @@ import BigInt import SoraFoundation import RobinHood -final class AssetDetailsPresenter { +final class AssetDetailsPresenter: PurchaseFlowManaging { weak var view: AssetDetailsViewProtocol? let wireframe: AssetDetailsWireframeProtocol let viewModelFactory: AssetDetailsViewModelFactoryProtocol @@ -95,22 +95,12 @@ final class AssetDetailsPresenter { } private func showPurchase() { - guard !purchaseActions.isEmpty else { - return - } - if purchaseActions.count == 1 { - wireframe.showPurchaseTokens( - from: view, - action: purchaseActions[0], - delegate: self - ) - } else { - wireframe.showPurchaseProviders( - from: view, - actions: purchaseActions, - delegate: self - ) - } + startPuchaseFlow( + from: view, + purchaseActions: purchaseActions, + wireframe: wireframe, + locale: selectedLocale + ) } private func showReceiveTokens() { @@ -253,19 +243,17 @@ extension AssetDetailsPresenter: Localizable { extension AssetDetailsPresenter: ModalPickerViewControllerDelegate { func modalPickerDidSelectModelAtIndex(_ index: Int, context _: AnyObject?) { - wireframe.showPurchaseTokens( + startPuchaseFlow( from: view, - action: purchaseActions[index], - delegate: self + purchaseAction: purchaseActions[index], + wireframe: wireframe, + locale: selectedLocale ) } } extension AssetDetailsPresenter: PurchaseDelegate { func purchaseDidComplete() { - let languages = selectedLocale.rLanguages - let message = R.string.localizable - .buyCompleted(preferredLanguages: languages) - wireframe.presentSuccessAlert(from: view, message: message) + wireframe.presentPurchaseDidComplete(view: view, locale: selectedLocale) } } diff --git a/novawallet/Modules/AssetDetails/AssetDetailsProtocols.swift b/novawallet/Modules/AssetDetails/AssetDetailsProtocols.swift index 89d5cb50d8..b02c76bb10 100644 --- a/novawallet/Modules/AssetDetails/AssetDetailsProtocols.swift +++ b/novawallet/Modules/AssetDetails/AssetDetailsProtocols.swift @@ -30,26 +30,15 @@ protocol AssetDetailsInteractorOutputProtocol: AnyObject { func didReceive(purchaseActions: [PurchaseAction]) } -protocol AssetDetailsWireframeProtocol: AnyObject { +protocol AssetDetailsWireframeProtocol: AnyObject, PurchasePresentable, AlertPresentable { func showSendTokens(from view: AssetDetailsViewProtocol?, chainAsset: ChainAsset) func showReceiveTokens( from view: AssetDetailsViewProtocol?, chainAsset: ChainAsset, metaChainAccountResponse: MetaChainAccountResponse ) - func showPurchaseProviders( - from view: AssetDetailsViewProtocol?, - actions: [PurchaseAction], - delegate: ModalPickerViewControllerDelegate - ) - func showPurchaseTokens( - from view: AssetDetailsViewProtocol?, - action: PurchaseAction, - delegate: PurchaseDelegate - ) func showNoSigning(from view: AssetDetailsViewProtocol?) func showLedgerNotSupport(for tokenName: String, from view: AssetDetailsViewProtocol?) - func presentSuccessAlert(from view: AssetDetailsViewProtocol?, message: String) func showLocks(from view: AssetDetailsViewProtocol?, model: AssetDetailsLocksViewModel) } diff --git a/novawallet/Modules/AssetList/AssetListInteractor.swift b/novawallet/Modules/AssetList/AssetListInteractor.swift index 5874b5b757..0055db28e4 100644 --- a/novawallet/Modules/AssetList/AssetListInteractor.swift +++ b/novawallet/Modules/AssetList/AssetListInteractor.swift @@ -80,6 +80,10 @@ final class AssetListInteractor: AssetListBaseInteractor { updateLocksSubscription(from: enabledChainChanges) } + private func providePolkadotStakingPromotion() { + presenter?.didReceivePromotionBanner(shouldShowPolkadotStaking: !settingsManager.polkadotStakingPromoSeen) + } + private func clearLocksSubscription() { assetLocksSubscriptions.values.forEach { $0.removeObserver(self) } assetLocksSubscriptions = [:] @@ -155,6 +159,7 @@ final class AssetListInteractor: AssetListBaseInteractor { provideHidesZeroBalances() provideWalletConnectSessionsCount() + providePolkadotStakingPromotion() subscribeChains() @@ -207,6 +212,11 @@ final class AssetListInteractor: AssetListBaseInteractor { } extension AssetListInteractor: AssetListInteractorInputProtocol { + func markPolkadotStakingPromotionSeen() { + settingsManager.polkadotStakingPromoSeen = true + providePolkadotStakingPromotion() + } + func refresh() { if let provider = priceSubscription { provider.refresh() diff --git a/novawallet/Modules/AssetList/AssetListPresenter.swift b/novawallet/Modules/AssetList/AssetListPresenter.swift index d4260a4dd5..8e7e55ec6c 100644 --- a/novawallet/Modules/AssetList/AssetListPresenter.swift +++ b/novawallet/Modules/AssetList/AssetListPresenter.swift @@ -20,6 +20,7 @@ final class AssetListPresenter { private var walletType: MetaAccountModelType? private var name: String? private var hidesZeroBalances: Bool? + private var shouldShowPolkadotPromotion: Bool = true private(set) var walletConnectSessionsCount: Int = 0 @@ -37,6 +38,16 @@ final class AssetListPresenter { self.localizationManager = localizationManager } + private func providePolkadotStakingPromotion() { + guard shouldShowPolkadotPromotion else { + return + } + + let viewModel = PromotionViewModelFactory.createPolkadotStakingPromotion(for: selectedLocale) + + view?.didReceivePromotion(viewModel: viewModel) + } + private func provideHeaderViewModel() { guard let walletType = walletType, let name = name else { return @@ -436,6 +447,22 @@ extension AssetListPresenter: AssetListPresenterProtocol { wireframe.showScan(from: view, delegate: self) } } + + func selectPromotion() { + shouldShowPolkadotPromotion = false + interactor.markPolkadotStakingPromotionSeen() + + wireframe.showStaking(from: view) + + view?.didClosePromotion() + } + + func closePromotion() { + shouldShowPolkadotPromotion = false + interactor.markPolkadotStakingPromotionSeen() + + view?.didClosePromotion() + } } extension AssetListPresenter: AssetListInteractorOutputProtocol { @@ -502,6 +529,11 @@ extension AssetListPresenter: AssetListInteractorOutputProtocol { func didCompleteRefreshing() { view?.didCompleteRefreshing() } + + func didReceivePromotionBanner(shouldShowPolkadotStaking: Bool) { + shouldShowPolkadotPromotion = shouldShowPolkadotStaking + providePolkadotStakingPromotion() + } } extension AssetListPresenter: Localizable { @@ -509,6 +541,7 @@ extension AssetListPresenter: Localizable { if let view = view, view.isSetup { updateAssetsView() updateNftView() + providePolkadotStakingPromotion() } } } diff --git a/novawallet/Modules/AssetList/AssetListProtocols.swift b/novawallet/Modules/AssetList/AssetListProtocols.swift index 3794b0e1b0..1d53253b78 100644 --- a/novawallet/Modules/AssetList/AssetListProtocols.swift +++ b/novawallet/Modules/AssetList/AssetListProtocols.swift @@ -7,6 +7,8 @@ protocol AssetListViewProtocol: ControllerBackedProtocol { func didReceiveHeader(viewModel: AssetListHeaderViewModel) func didReceiveGroups(viewModel: AssetListViewModel) func didReceiveNft(viewModel: AssetListNftsViewModel?) + func didReceivePromotion(viewModel: PromotionBannerView.ViewModel) + func didClosePromotion() func didCompleteRefreshing() } @@ -24,6 +26,8 @@ protocol AssetListPresenterProtocol: AnyObject { func receive() func buy() func presentWalletConnect() + func selectPromotion() + func closePromotion() } protocol AssetListInteractorInputProtocol { @@ -32,6 +36,7 @@ protocol AssetListInteractorInputProtocol { func refresh() func connectWalletConnect(uri: String) func retryFetchWalletConnectSessionsCount() + func markPolkadotStakingPromotionSeen() } protocol AssetListInteractorOutputProtocol { @@ -48,6 +53,7 @@ protocol AssetListInteractorOutputProtocol { func didReceiveWalletConnect(sessionsCount: Int) func didReceiveWalletConnect(error: WalletConnectSessionsError) func didCompleteRefreshing() + func didReceivePromotionBanner(shouldShowPolkadotStaking: Bool) } protocol AssetListWireframeProtocol: AnyObject, WalletSwitchPresentable, AlertPresentable, ErrorPresentable, @@ -73,6 +79,8 @@ protocol AssetListWireframeProtocol: AnyObject, WalletSwitchPresentable, AlertPr ) func showBuyTokens(from view: AssetListViewProtocol?) + + func showStaking(from view: AssetListViewProtocol?) } typealias WalletConnectSessionsError = WalletConnectSessionsInteractorError diff --git a/novawallet/Modules/AssetList/AssetListViewController.swift b/novawallet/Modules/AssetList/AssetListViewController.swift index d166d3367a..f40a0277a1 100644 --- a/novawallet/Modules/AssetList/AssetListViewController.swift +++ b/novawallet/Modules/AssetList/AssetListViewController.swift @@ -13,6 +13,7 @@ final class AssetListViewController: UIViewController, ViewHolder { private var headerViewModel: AssetListHeaderViewModel? private var groupsViewModel: AssetListViewModel = .init(isFiltered: false, listState: .list(groups: [])) private var nftViewModel: AssetListNftsViewModel? + private var promotionBannerViewModel: PromotionBannerView.ViewModel? init(presenter: AssetListPresenterProtocol, localizationManager: LocalizationManagerProtocol) { self.presenter = presenter @@ -50,6 +51,7 @@ final class AssetListViewController: UIViewController, ViewHolder { rootView.collectionView.registerCellClass(AssetListSettingsCell.self) rootView.collectionView.registerCellClass(AssetListEmptyCell.self) rootView.collectionView.registerCellClass(AssetListNftsCell.self) + rootView.collectionView.registerCellClass(AssetListBannerCell.self) rootView.collectionView.registerClass( AssetListNetworkView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader @@ -147,7 +149,7 @@ extension AssetListViewController: UICollectionViewDelegateFlowLayout { height: AssetListMeasurement.assetHeaderHeight ) - case .summary, .settings, .nfts: + case .summary, .settings, .nfts, .promotion: return .zero } } @@ -169,6 +171,8 @@ extension AssetListViewController: UICollectionViewDelegateFlowLayout { } case .yourNfts: presenter.selectNfts() + case .banner: + presenter.selectPromotion() } } @@ -185,7 +189,8 @@ extension AssetListViewController: UICollectionViewDelegateFlowLayout { layout _: UICollectionViewLayout, insetForSectionAt section: Int ) -> UIEdgeInsets { - AssetListFlowLayout.SectionType(section: section).insets + let sectionType = AssetListFlowLayout.SectionType(section: section) + return rootView.collectionViewLayout.sectionInsets(for: sectionType) } } @@ -200,6 +205,8 @@ extension AssetListViewController: UICollectionViewDataSource { return headerViewModel != nil ? 2 : 0 case .nfts: return nftViewModel != nil ? 1 : 0 + case .promotion: + return promotionBannerViewModel != nil ? 1 : 0 case .settings: return groupsViewModel.listState.isEmpty ? 2 : 1 case .assetGroup: @@ -365,6 +372,24 @@ extension AssetListViewController: UICollectionViewDataSource { return cell } + private func providePromotionBannerCell( + _ collectionView: UICollectionView, + indexPath: IndexPath + ) -> AssetListBannerCell { + let cell = collectionView.dequeueReusableCellWithType( + AssetListBannerCell.self, + for: indexPath + )! + + if let viewModel = promotionBannerViewModel { + cell.bind(viewModel: viewModel) + } + + cell.bannerView.delegate = self + + return cell + } + func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath @@ -376,6 +401,8 @@ extension AssetListViewController: UICollectionViewDataSource { return provideTotalBalanceCell(collectionView, indexPath: indexPath) case .yourNfts: return provideYourNftsCell(collectionView, indexPath: indexPath) + case .banner: + return providePromotionBannerCell(collectionView, indexPath: indexPath) case .settings: return provideSettingsCell(collectionView, indexPath: indexPath) case .emptyState: @@ -439,9 +466,42 @@ extension AssetListViewController: AssetListViewProtocol { nftViewModel = viewModel rootView.collectionView.reloadData() + + let isNftActive = viewModel != nil + rootView.collectionViewLayout.setNftsActive(isNftActive) } func didCompleteRefreshing() { rootView.collectionView.refreshControl?.endRefreshing() } + + func didReceivePromotion(viewModel: PromotionBannerView.ViewModel) { + promotionBannerViewModel = viewModel + + rootView.collectionView.reloadData() + + let height = AssetListBannerCell.estimateHeight(for: viewModel) + rootView.collectionViewLayout.activatePromotionWithHeight(height) + } + + func didClosePromotion() { + guard promotionBannerViewModel != nil else { + return + } + + rootView.collectionView.performBatchUpdates { [weak self] in + self?.promotionBannerViewModel = nil + + let indexPath = AssetListFlowLayout.CellType.banner.indexPath + self?.rootView.collectionView.deleteItems(at: [indexPath]) + } + + rootView.collectionViewLayout.deactivatePromotion() + } +} + +extension AssetListViewController: PromotionBannerViewDelegate { + func promotionBannerDidRequestClose(view _: PromotionBannerView) { + presenter.closePromotion() + } } diff --git a/novawallet/Modules/AssetList/AssetListWireframe.swift b/novawallet/Modules/AssetList/AssetListWireframe.swift index 4839d46da8..553af48c50 100644 --- a/novawallet/Modules/AssetList/AssetListWireframe.swift +++ b/novawallet/Modules/AssetList/AssetListWireframe.swift @@ -159,4 +159,12 @@ final class AssetListWireframe: AssetListWireframeProtocol { walletConnectView.controller.hidesBottomBarWhenPushed = true view?.controller.navigationController?.pushViewController(walletConnectView.controller, animated: true) } + + func showStaking(from view: AssetListViewProtocol?) { + guard let tabBarController = view?.controller.navigationController?.tabBarController else { + return + } + + tabBarController.selectedIndex = MainTabBarIndex.staking + } } diff --git a/novawallet/Modules/AssetList/Base/AssetListBaseBuilder.swift b/novawallet/Modules/AssetList/Base/AssetListBaseBuilder.swift index 034f5bc777..f1dfd64993 100644 --- a/novawallet/Modules/AssetList/Base/AssetListBaseBuilder.swift +++ b/novawallet/Modules/AssetList/Base/AssetListBaseBuilder.swift @@ -151,9 +151,7 @@ class AssetListBaseBuilder { case let .success(maybeAmount): if let amount = maybeAmount { balanceResults[chainAssetId] = .success(amount.total) - if let balance = amount.balance { - balances[chainAssetId] = .success(balance) - } + balances[chainAssetId] = amount.balance.map { .success($0) } } else if balanceResults[chainAssetId] == nil { balanceResults[chainAssetId] = .success(0) } diff --git a/novawallet/Modules/AssetList/View/AssetListBannerCell.swift b/novawallet/Modules/AssetList/View/AssetListBannerCell.swift new file mode 100644 index 0000000000..793144440d --- /dev/null +++ b/novawallet/Modules/AssetList/View/AssetListBannerCell.swift @@ -0,0 +1,37 @@ +import UIKit + +final class AssetListBannerCell: UICollectionViewCell { + let bannerView = PromotionBannerView() + + override init(frame: CGRect) { + super.init(frame: frame) + + setupLayout() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupLayout() { + contentView.addSubview(bannerView) + + bannerView.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(UIConstants.horizontalInset) + make.top.bottom.equalToSuperview() + } + } + + func bind(viewModel: PromotionBannerView.ViewModel) { + bannerView.bind(viewModel: viewModel) + } +} + +extension AssetListBannerCell { + static func estimateHeight(for viewModel: PromotionBannerView.ViewModel) -> CGFloat { + let width = UIScreen.main.bounds.width - 2 * UIConstants.horizontalInset + + return PromotionBannerView.estimateHeight(for: viewModel, width: width) + } +} diff --git a/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift b/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift index 42edeb2a71..0584ae6260 100644 --- a/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift +++ b/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift @@ -6,20 +6,31 @@ enum AssetListMeasurement { static let totalBalanceWithLocksHeight: CGFloat = 200.0 static let settingsHeight: CGFloat = 56.0 static let nftsHeight = 56.0 + static let bannerHeight = 102.0 static let assetHeight: CGFloat = 56.0 static let assetHeaderHeight: CGFloat = 45.0 static let emptyStateCellHeight: CGFloat = 230 static let decorationInset: CGFloat = 8.0 + static let promotionInsets = UIEdgeInsets(top: 0, left: 0, bottom: 12, right: 0) + static let summaryInsets = UIEdgeInsets(top: 0, left: 0, bottom: 12, right: 0) + static let nftsInsets = UIEdgeInsets(top: 0, left: 0, bottom: 12, right: 0) + static let settingsInsets = UIEdgeInsets.zero + static let assetGroupInsets = UIEdgeInsets(top: 2.0, left: 0, bottom: 16.0, right: 0) } final class AssetListFlowLayout: UICollectionViewFlowLayout { static let assetGroupDecoration = "assetGroupDecoration" private var totalBalanceHeight: CGFloat = AssetListMeasurement.totalBalanceHeight + private var promotionHeight: CGFloat = AssetListMeasurement.bannerHeight + private var promotionInsets: UIEdgeInsets = .zero + private var nftsInsets: UIEdgeInsets = .zero + enum SectionType: CaseIterable { case summary case nfts case settings + case promotion case assetGroup init(section: Int) { @@ -29,6 +40,8 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { case 1: self = .nfts case 2: + self = .promotion + case 3: self = .settings default: self = .assetGroup @@ -41,10 +54,12 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { return 0 case .nfts: return 1 - case .settings: + case .promotion: return 2 - case .assetGroup: + case .settings: return 3 + case .assetGroup: + return 4 } } @@ -64,29 +79,17 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { switch self { case .summary: return 10.0 - case .settings, .assetGroup, .nfts: + case .settings, .assetGroup, .nfts, .promotion: return 0 } } - - var insets: UIEdgeInsets { - switch self { - case .summary: - return UIEdgeInsets(top: 0, left: 0, bottom: 12, right: 0) - case .nfts: - return UIEdgeInsets(top: 0, left: 0, bottom: 8, right: 0) - case .settings: - return .zero - case .assetGroup: - return UIEdgeInsets(top: 2.0, left: 0, bottom: 16.0, right: 0) - } - } } enum CellType { case account case totalBalance case yourNfts + case banner case settings case asset(sectionIndex: Int, itemIndex: Int) case emptyState @@ -98,6 +101,8 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { case 1: self = .yourNfts case 2: + self = .banner + case 3: self = indexPath.row == 0 ? .settings : .emptyState default: self = .asset(sectionIndex: indexPath.section, itemIndex: indexPath.row) @@ -112,10 +117,12 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { return IndexPath(item: 1, section: 0) case .yourNfts: return IndexPath(item: 0, section: 1) - case .settings: + case .banner: return IndexPath(item: 0, section: 2) + case .settings: + return IndexPath(item: 0, section: 3) case .emptyState: - return IndexPath(item: 1, section: 2) + return IndexPath(item: 1, section: 3) case let .asset(sectionIndex, itemIndex): return IndexPath(item: itemIndex, section: sectionIndex) } @@ -158,6 +165,7 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { updateItemsBackgroundAttributesIfNeeded() } + // swiftlint:disable:next function_body_length private func updateItemsBackgroundAttributesIfNeeded() { guard let collectionView = collectionView, @@ -176,9 +184,9 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { totalBalanceHeight } - groupY += SectionType.summary.insets.top + SectionType.summary.insets.bottom + groupY += AssetListMeasurement.summaryInsets.top + AssetListMeasurement.summaryInsets.bottom - groupY += SectionType.nfts.insets.top + SectionType.nfts.insets.bottom + groupY += nftsInsets.top + nftsInsets.bottom let hasNfts = collectionView.numberOfItems(inSection: SectionType.nfts.index) > 0 @@ -186,8 +194,16 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { groupY += AssetListMeasurement.nftsHeight } - groupY += SectionType.settings.insets.top + AssetListMeasurement.settingsHeight + - SectionType.settings.insets.bottom + groupY += promotionInsets.top + promotionInsets.bottom + + let hasPromotion = collectionView.numberOfItems(inSection: SectionType.promotion.index) > 0 + + if hasPromotion { + groupY += promotionHeight + } + + groupY += AssetListMeasurement.settingsInsets.top + AssetListMeasurement.settingsHeight + + AssetListMeasurement.settingsInsets.bottom let initAttributes = [UICollectionViewLayoutAttributes]() let (attributes, _) = (0 ..< groupsCount).reduce((initAttributes, groupY)) { result, groupIndex in @@ -199,7 +215,7 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { let contentHeight = AssetListMeasurement.assetHeaderHeight + CGFloat(numberOfItems) * AssetListMeasurement.assetHeight - let decorationHeight = SectionType.assetGroup.insets.top + contentHeight + + let decorationHeight = AssetListMeasurement.assetGroupInsets.top + contentHeight + AssetListMeasurement.decorationInset let itemsDecorationAttributes = UICollectionViewLayoutAttributes( @@ -215,8 +231,8 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { itemsDecorationAttributes.frame = CGRect(origin: origin, size: size) itemsDecorationAttributes.zIndex = -1 - let newPosition = positionY + SectionType.assetGroup.insets.top + contentHeight + - SectionType.assetGroup.insets.bottom + let newPosition = positionY + AssetListMeasurement.assetGroupInsets.top + contentHeight + + AssetListMeasurement.assetGroupInsets.bottom let newAttributes = attributes + [itemsDecorationAttributes] @@ -234,6 +250,40 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { invalidateLayout() } + func activatePromotionWithHeight(_ height: CGFloat) { + let newInsets = AssetListMeasurement.promotionInsets + + guard height != promotionHeight || promotionInsets != newInsets else { + return + } + + promotionHeight = height + promotionInsets = newInsets + invalidateLayout() + } + + func deactivatePromotion() { + let newInsets = UIEdgeInsets.zero + + guard promotionInsets != newInsets else { + return + } + + promotionInsets = newInsets + invalidateLayout() + } + + func setNftsActive(_ isActive: Bool) { + let newInsets = isActive ? AssetListMeasurement.nftsInsets : .zero + + guard nftsInsets != newInsets else { + return + } + + nftsInsets = newInsets + invalidateLayout() + } + func cellHeight(for type: CellType) -> CGFloat { switch type { case .account: @@ -242,6 +292,8 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { return totalBalanceHeight case .yourNfts: return AssetListMeasurement.nftsHeight + case .banner: + return promotionHeight case .settings: return AssetListMeasurement.settingsHeight case .emptyState: @@ -250,4 +302,19 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { return AssetListMeasurement.assetHeight } } + + func sectionInsets(for type: SectionType) -> UIEdgeInsets { + switch type { + case .summary: + return AssetListMeasurement.summaryInsets + case .nfts: + return nftsInsets + case .promotion: + return promotionInsets + case .settings: + return AssetListMeasurement.settingsInsets + case .assetGroup: + return AssetListMeasurement.assetGroupInsets + } + } } diff --git a/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift b/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift index 64d371afda..29a35fd558 100644 --- a/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift +++ b/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift @@ -3,7 +3,7 @@ import BigInt import RobinHood import SoraFoundation -final class BuyAssetOperationPresenter: AssetsSearchPresenter { +final class BuyAssetOperationPresenter: AssetsSearchPresenter, PurchaseFlowManaging { var buyAssetWireframe: BuyAssetOperationWireframeProtocol? { wireframe as? BuyAssetOperationWireframeProtocol } @@ -32,22 +32,6 @@ final class BuyAssetOperationPresenter: AssetsSearchPresenter { ) } - private func showPurchase() { - if purchaseActions.count == 1 { - buyAssetWireframe?.showPurchaseTokens( - from: view, - action: purchaseActions[0], - delegate: self - ) - } else { - buyAssetWireframe?.showPurchaseProviders( - from: view, - actions: purchaseActions, - delegate: self - ) - } - } - override func selectAsset(for chainAssetId: ChainAssetId) { guard let chainAsset = result?.state.chainAsset(for: chainAssetId) else { return @@ -70,7 +54,15 @@ final class BuyAssetOperationPresenter: AssetsSearchPresenter { on: view, by: commonCheckResult, successRouteClosure: { [weak self] in - self?.showPurchase() + guard let self = self else { + return + } + self.startPuchaseFlow( + from: self.view, + purchaseActions: self.purchaseActions, + wireframe: self.buyAssetWireframe, + locale: selectedLocale + ) } ) case .noBuyOptions: @@ -81,19 +73,17 @@ final class BuyAssetOperationPresenter: AssetsSearchPresenter { extension BuyAssetOperationPresenter: ModalPickerViewControllerDelegate { func modalPickerDidSelectModelAtIndex(_ index: Int, context _: AnyObject?) { - buyAssetWireframe?.showPurchaseTokens( + startPuchaseFlow( from: view, - action: purchaseActions[index], - delegate: self + purchaseAction: purchaseActions[index], + wireframe: buyAssetWireframe, + locale: selectedLocale ) } } extension BuyAssetOperationPresenter: PurchaseDelegate { func purchaseDidComplete() { - let languages = localizationManager?.selectedLocale.rLanguages - let message = R.string.localizable - .buyCompleted(preferredLanguages: languages) - buyAssetWireframe?.presentSuccessAlert(from: view, message: message) + buyAssetWireframe?.presentPurchaseDidComplete(view: view, locale: selectedLocale) } } diff --git a/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationWireframe.swift b/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationWireframe.swift index d50235e4f0..52e0234fd7 100644 --- a/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationWireframe.swift +++ b/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationWireframe.swift @@ -1,59 +1,9 @@ import UIKit import SoraUI -protocol BuyAssetOperationWireframeProtocol: AssetsSearchWireframeProtocol, MessageSheetPresentable { - func showPurchaseProviders( - from view: ControllerBackedProtocol?, - actions: [PurchaseAction], - delegate: ModalPickerViewControllerDelegate - ) - func showPurchaseTokens( - from view: ControllerBackedProtocol?, - action: PurchaseAction, - delegate: PurchaseDelegate - ) - func presentSuccessAlert(from view: ControllerBackedProtocol?, message: String) -} - -final class BuyAssetOperationWireframe: BuyAssetOperationWireframeProtocol { - func showPurchaseProviders( - from view: ControllerBackedProtocol?, - actions: [PurchaseAction], - delegate: ModalPickerViewControllerDelegate - ) { - guard let pickerView = ModalPickerFactory.createPickerForList( - actions, - delegate: delegate, - context: nil - ) else { - return - } - guard let navigationController = view?.controller.navigationController else { - return - } - navigationController.present(pickerView, animated: true) - } +protocol BuyAssetOperationWireframeProtocol: AssetsSearchWireframeProtocol, MessageSheetPresentable, PurchasePresentable, AlertPresentable {} - func showPurchaseTokens( - from view: ControllerBackedProtocol?, - action: PurchaseAction, - delegate: PurchaseDelegate - ) { - guard let purchaseView = PurchaseViewFactory.createView( - for: action, - delegate: delegate - ) else { - return - } - purchaseView.controller.modalPresentationStyle = .fullScreen - view?.controller.present(purchaseView.controller, animated: true) - } - - func presentSuccessAlert(from view: ControllerBackedProtocol?, message: String) { - let alertController = ModalAlertFactory.createMultilineSuccessAlert(message) - view?.controller.present(alertController, animated: true) - } -} +final class BuyAssetOperationWireframe: BuyAssetOperationWireframeProtocol {} extension BuyAssetOperationWireframe: AssetsSearchWireframeProtocol { func close(view: AssetsSearchViewProtocol?) { diff --git a/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift b/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift index ec16dfb8a1..36a26fe054 100644 --- a/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift +++ b/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift @@ -172,6 +172,7 @@ extension AssetsSearchViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: true) + keyboardAppearanceStrategy.onCellSelected(for: rootView.searchBar.textField) let cellType = AssetsSearchFlowLayout.CellType(indexPath: indexPath) diff --git a/novawallet/Modules/Nft/NftList/NftListInteractor.swift b/novawallet/Modules/Nft/NftList/NftListInteractor.swift index 09111d9c0d..842d9e56ca 100644 --- a/novawallet/Modules/Nft/NftList/NftListInteractor.swift +++ b/novawallet/Modules/Nft/NftList/NftListInteractor.swift @@ -6,7 +6,7 @@ enum NftListInteractorError: Error { } final class NftListInteractor { - weak var presenter: NftListInteractorOutputProtocol! + weak var presenter: NftListInteractorOutputProtocol? let wallet: MetaAccountModel let chainRegistry: ChainRegistryProtocol @@ -115,7 +115,7 @@ final class NftListInteractor { } if !nftChanges.isEmpty { - presenter.didReceiveNft(changes: nftChanges) + presenter?.didReceiveNft(changes: nftChanges) } if hasChanges { @@ -233,11 +233,11 @@ extension NftListInteractor: NftListInteractorInputProtocol { func getNftForId(_ identifier: NftModel.Id) { guard let nft = nfts[identifier] else { - presenter.didReceive(error: NftListInteractorError.nftUnavailable) + presenter?.didReceive(error: NftListInteractorError.nftUnavailable) return } - presenter.didReceiveNft(nft) + presenter?.didReceiveNft(nft) } } @@ -247,10 +247,10 @@ extension NftListInteractor: PriceLocalStorageSubscriber, PriceLocalSubscription case let .success(optionalPriceData): if let priceData = optionalPriceData { let changes = updateNftsFromPrice(priceData, priceId: priceId) - presenter.didReceiveNft(changes: changes) + presenter?.didReceiveNft(changes: changes) } case let .failure(error): - presenter.didReceive(error: error) + presenter?.didReceive(error: error) } } } @@ -260,9 +260,9 @@ extension NftListInteractor: NftLocalStorageSubscriber, NftLocalSubscriptionHand switch result { case let .success(changes): let changes = updateNftsFromModel(changes: changes) - presenter.didReceiveNft(changes: changes) + presenter?.didReceiveNft(changes: changes) case let .failure(error): - presenter.didReceive(error: error) + presenter?.didReceive(error: error) } } } diff --git a/novawallet/Modules/Staking/Dashboard/StakingDashboardInteractor.swift b/novawallet/Modules/Staking/Dashboard/StakingDashboardInteractor.swift index af2861919a..085c381999 100644 --- a/novawallet/Modules/Staking/Dashboard/StakingDashboardInteractor.swift +++ b/novawallet/Modules/Staking/Dashboard/StakingDashboardInteractor.swift @@ -249,9 +249,13 @@ extension StakingDashboardInteractor: PriceLocalStorageSubscriber, PriceLocalSub extension StakingDashboardInteractor: EventVisitorProtocol { func processSelectedAccountChanged(event _: SelectedAccountChanged) { + guard let wallet = walletSettings.value else { + return + } + provideWallet() - syncService?.update(selectedMetaAccount: walletSettings.value) + syncService?.update(selectedMetaAccount: wallet) resetDashboardItemsSubscription() resetBalanceSubscription() } diff --git a/novawallet/Modules/Staking/Model/Relaychain/ElectedAndPrefValidators.swift b/novawallet/Modules/Staking/Model/Relaychain/ElectedAndPrefValidators.swift index 415ca13ce4..bfb72efa8a 100644 --- a/novawallet/Modules/Staking/Model/Relaychain/ElectedAndPrefValidators.swift +++ b/novawallet/Modules/Staking/Model/Relaychain/ElectedAndPrefValidators.swift @@ -1,6 +1,6 @@ import Foundation -struct ElectedAndPrefValidators { +struct ElectedAndPrefValidators: Equatable { let electedValidators: [ElectedValidatorInfo] let preferredValidators: [SelectedValidatorInfo] diff --git a/novawallet/Modules/Staking/Model/Relaychain/PreparedNomination.swift b/novawallet/Modules/Staking/Model/Relaychain/PreparedNomination.swift index 711e1db363..66652675d6 100644 --- a/novawallet/Modules/Staking/Model/Relaychain/PreparedNomination.swift +++ b/novawallet/Modules/Staking/Model/Relaychain/PreparedNomination.swift @@ -6,7 +6,7 @@ struct PreparedNomination { let maxTargets: Int } -struct PreparedValidators { +struct PreparedValidators: Equatable { let targets: [SelectedValidatorInfo] let maxTargets: Int let electedAndPrefValidators: ElectedAndPrefValidators diff --git a/novawallet/Modules/Staking/Model/Relaychain/SelectedStakingOption.swift b/novawallet/Modules/Staking/Model/Relaychain/SelectedStakingOption.swift index c436d79df7..08dd3250e8 100644 --- a/novawallet/Modules/Staking/Model/Relaychain/SelectedStakingOption.swift +++ b/novawallet/Modules/Staking/Model/Relaychain/SelectedStakingOption.swift @@ -1,6 +1,6 @@ import Foundation -enum SelectedStakingOption { +enum SelectedStakingOption: Equatable { case direct(PreparedValidators) case pool(NominationPools.SelectedPool) diff --git a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchInteractor.swift b/novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchInteractor.swift similarity index 100% rename from novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchInteractor.swift rename to novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchInteractor.swift diff --git a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchManager.swift b/novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchManager.swift similarity index 100% rename from novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchManager.swift rename to novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchManager.swift diff --git a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchOperationFactory.swift b/novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchOperationFactory.swift similarity index 100% rename from novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchOperationFactory.swift rename to novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchOperationFactory.swift diff --git a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchPresenter.swift b/novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchPresenter.swift similarity index 100% rename from novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchPresenter.swift rename to novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchPresenter.swift diff --git a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchProtocols.swift b/novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchProtocols.swift similarity index 100% rename from novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchProtocols.swift rename to novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchProtocols.swift diff --git a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewController.swift b/novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchViewController.swift similarity index 99% rename from novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewController.swift rename to novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchViewController.swift index 828a45a81a..b8ee15226d 100644 --- a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewController.swift +++ b/novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchViewController.swift @@ -133,7 +133,7 @@ extension NominationPoolSearchViewController: UITableViewDelegate { guard let viewModels = state.viewModel, !viewModels.isEmpty else { return 0 } - return 26 + return 29 } func tableView(_ tableView: UITableView, viewForHeaderInSection _: Int) -> UIView? { @@ -185,7 +185,7 @@ extension NominationPoolSearchViewController: EmptyStateDataSource { } var contentViewForEmptyState: UIView { - rootView.emptyStateContainer + rootView.contentView } var verticalSpacingForEmptyState: CGFloat? { diff --git a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewFactory.swift b/novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchViewFactory.swift similarity index 100% rename from novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewFactory.swift rename to novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchViewFactory.swift diff --git a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewLayout.swift b/novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchViewLayout.swift similarity index 100% rename from novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewLayout.swift rename to novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchViewLayout.swift diff --git a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchWireframe.swift b/novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchWireframe.swift similarity index 100% rename from novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchWireframe.swift rename to novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchWireframe.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/Model/ButtonViewModel.swift b/novawallet/Modules/Staking/NominationPools/Selection/Model/ButtonViewModel.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/Model/ButtonViewModel.swift rename to novawallet/Modules/Staking/NominationPools/Selection/Model/ButtonViewModel.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/Model/StakingSelectPoolViewModelFactory.swift b/novawallet/Modules/Staking/NominationPools/Selection/Model/StakingSelectPoolViewModelFactory.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/Model/StakingSelectPoolViewModelFactory.swift rename to novawallet/Modules/Staking/NominationPools/Selection/Model/StakingSelectPoolViewModelFactory.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolInteractor.swift b/novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolInteractor.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolInteractor.swift rename to novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolInteractor.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolPresenter.swift b/novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolPresenter.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolPresenter.swift rename to novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolPresenter.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolProtocols.swift b/novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolProtocols.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolProtocols.swift rename to novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolProtocols.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolViewController.swift b/novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolViewController.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolViewController.swift rename to novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolViewController.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolViewFactory.swift b/novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolViewFactory.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolViewFactory.swift rename to novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolViewFactory.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolWireframe.swift b/novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolWireframe.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/StakingSelectPoolWireframe.swift rename to novawallet/Modules/Staking/NominationPools/Selection/StakingSelectPoolWireframe.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/View/StakingPoolTableViewCell.swift b/novawallet/Modules/Staking/NominationPools/Selection/View/StakingPoolTableViewCell.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/View/StakingPoolTableViewCell.swift rename to novawallet/Modules/Staking/NominationPools/Selection/View/StakingPoolTableViewCell.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/View/StakingPoolView.swift b/novawallet/Modules/Staking/NominationPools/Selection/View/StakingPoolView.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/View/StakingPoolView.swift rename to novawallet/Modules/Staking/NominationPools/Selection/View/StakingPoolView.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/View/StakingSelectPoolListHeaderView.swift b/novawallet/Modules/Staking/NominationPools/Selection/View/StakingSelectPoolListHeaderView.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/View/StakingSelectPoolListHeaderView.swift rename to novawallet/Modules/Staking/NominationPools/Selection/View/StakingSelectPoolListHeaderView.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/View/StakingSelectPoolViewLayout.swift b/novawallet/Modules/Staking/NominationPools/Selection/View/StakingSelectPoolViewLayout.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/View/StakingSelectPoolViewLayout.swift rename to novawallet/Modules/Staking/NominationPools/Selection/View/StakingSelectPoolViewLayout.swift diff --git a/novawallet/Modules/Staking/StakingSelectPool/View/StakingSelectPoolViewStyles.swift b/novawallet/Modules/Staking/NominationPools/Selection/View/StakingSelectPoolViewStyles.swift similarity index 100% rename from novawallet/Modules/Staking/StakingSelectPool/View/StakingSelectPoolViewStyles.swift rename to novawallet/Modules/Staking/NominationPools/Selection/View/StakingSelectPoolViewStyles.swift diff --git a/novawallet/Modules/Staking/Parachain/ParaStkCollatorsSearch/ParaStkCollatorsSearchViewController.swift b/novawallet/Modules/Staking/Parachain/ParaStkCollatorsSearch/ParaStkCollatorsSearchViewController.swift index f25f53d910..c7dd5f3c6d 100644 --- a/novawallet/Modules/Staking/Parachain/ParaStkCollatorsSearch/ParaStkCollatorsSearchViewController.swift +++ b/novawallet/Modules/Staking/Parachain/ParaStkCollatorsSearch/ParaStkCollatorsSearchViewController.swift @@ -170,7 +170,7 @@ extension ParaStkCollatorsSearchViewController: EmptyStateDataSource { } var contentViewForEmptyState: UIView { - rootView.emptyStateContainer + rootView.contentView } var verticalSpacingForEmptyState: CGFloat? { diff --git a/novawallet/Modules/Staking/Protocols/NominationPoolErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/NominationPoolErrorPresentable.swift index dbc152cfe9..d30b59c1e3 100644 --- a/novawallet/Modules/Staking/Protocols/NominationPoolErrorPresentable.swift +++ b/novawallet/Modules/Staking/Protocols/NominationPoolErrorPresentable.swift @@ -1,6 +1,6 @@ import Foundation -protocol NominationPoolErrorPresentable: BaseErrorPresentable { +protocol NominationPoolErrorPresentable: StakingBaseErrorPresentable { func presentNominationPoolHasNoApy( from view: ControllerBackedProtocol, action: @escaping () -> Void, @@ -23,14 +23,6 @@ protocol NominationPoolErrorPresentable: BaseErrorPresentable { locale: Locale ) - func presentCrossedMinStake( - from view: ControllerBackedProtocol?, - minStake: String, - remaining: String, - action: @escaping () -> Void, - locale: Locale - ) - func presentNoProfitAfterClaimRewards( from view: ControllerBackedProtocol, action: @escaping () -> Void, @@ -129,39 +121,6 @@ extension NominationPoolErrorPresentable where Self: AlertPresentable & ErrorPre ) } - func presentCrossedMinStake( - from view: ControllerBackedProtocol?, - minStake: String, - remaining: String, - action: @escaping () -> Void, - locale: Locale - ) { - let title = R.string.localizable.stakingUnstakeCrossedMinTitle(preferredLanguages: locale.rLanguages) - let message = R.string.localizable.stakingUnstakeCrossedMinMessage( - minStake, - remaining, - preferredLanguages: locale.rLanguages - ) - - let cancelAction = AlertPresentableAction( - title: R.string.localizable.commonCancel(preferredLanguages: locale.rLanguages) - ) - - let unstakeAllAction = AlertPresentableAction( - title: R.string.localizable.stakingUnstakeAll(preferredLanguages: locale.rLanguages), - handler: action - ) - - let viewModel = AlertPresentableViewModel( - title: title, - message: message, - actions: [cancelAction, unstakeAllAction], - closeAction: nil - ) - - present(viewModel: viewModel, style: .alert, from: view) - } - func presentNoProfitAfterClaimRewards( from view: ControllerBackedProtocol, action: @escaping () -> Void, diff --git a/novawallet/Modules/Staking/Protocols/StakingBaseErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/StakingBaseErrorPresentable.swift new file mode 100644 index 0000000000..716fe89aed --- /dev/null +++ b/novawallet/Modules/Staking/Protocols/StakingBaseErrorPresentable.swift @@ -0,0 +1,46 @@ +import Foundation + +protocol StakingBaseErrorPresentable: BaseErrorPresentable { + func presentCrossedMinStake( + from view: ControllerBackedProtocol?, + minStake: String, + remaining: String, + action: @escaping () -> Void, + locale: Locale + ) +} + +extension StakingBaseErrorPresentable where Self: AlertPresentable & ErrorPresentable { + func presentCrossedMinStake( + from view: ControllerBackedProtocol?, + minStake: String, + remaining: String, + action: @escaping () -> Void, + locale: Locale + ) { + let title = R.string.localizable.stakingUnstakeCrossedMinTitle(preferredLanguages: locale.rLanguages) + let message = R.string.localizable.stakingUnstakeCrossedMinMessage( + minStake, + remaining, + preferredLanguages: locale.rLanguages + ) + + let cancelAction = AlertPresentableAction( + title: R.string.localizable.commonCancel(preferredLanguages: locale.rLanguages) + ) + + let unstakeAllAction = AlertPresentableAction( + title: R.string.localizable.stakingUnstakeAll(preferredLanguages: locale.rLanguages), + handler: action + ) + + let viewModel = AlertPresentableViewModel( + title: title, + message: message, + actions: [cancelAction, unstakeAllAction], + closeAction: nil + ) + + present(viewModel: viewModel, style: .alert, from: view) + } +} diff --git a/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift index 945493a002..2002120716 100644 --- a/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift +++ b/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift @@ -7,7 +7,7 @@ struct NPoolsEDViolationErrorParams { let maxStake: String } -protocol StakingErrorPresentable: BaseErrorPresentable { +protocol StakingErrorPresentable: StakingBaseErrorPresentable { func presentAmountTooLow(value: String, from view: ControllerBackedProtocol, locale: Locale?) func presentMissingController( @@ -37,12 +37,6 @@ protocol StakingErrorPresentable: BaseErrorPresentable { locale: Locale? ) - func presentStashKilledAfterUnbond( - from view: ControllerBackedProtocol, - action: @escaping () -> Void, - locale: Locale? - ) - func presentUnbondingLimitReached(from view: ControllerBackedProtocol?, locale: Locale?) func presentNoRedeemables(from view: ControllerBackedProtocol?, locale: Locale?) func presentControllerIsAlreadyUsed(from view: ControllerBackedProtocol?, locale: Locale?) @@ -53,7 +47,11 @@ protocol StakingErrorPresentable: BaseErrorPresentable { locale: Locale? ) - func presentMaxNumberOfNominatorsReached(from view: ControllerBackedProtocol?, locale: Locale?) + func presentMaxNumberOfNominatorsReached( + from view: ControllerBackedProtocol?, + stakingType: String, + locale: Locale? + ) func presentMinRewardableStakeViolated( from view: ControllerBackedProtocol, @@ -171,19 +169,6 @@ extension StakingErrorPresentable where Self: AlertPresentable & ErrorPresentabl ) } - func presentStashKilledAfterUnbond( - from view: ControllerBackedProtocol, - action: @escaping () -> Void, - locale: Locale? - ) { - let title = R.string.localizable - .stakingUnbondingAllTitle(preferredLanguages: locale?.rLanguages) - let message = R.string.localizable - .stakingUnbondingAllMessage(preferredLanguages: locale?.rLanguages) - - presentWarning(for: title, message: message, action: action, view: view, locale: locale) - } - func presentUnbondingLimitReached(from view: ControllerBackedProtocol?, locale: Locale?) { let message = R.string.localizable.stakingUnbondingLimitReachedTitle(preferredLanguages: locale?.rLanguages) let title = R.string.localizable.commonErrorGeneralTitle(preferredLanguages: locale?.rLanguages) @@ -228,12 +213,17 @@ extension StakingErrorPresentable where Self: AlertPresentable & ErrorPresentabl ) } - func presentMaxNumberOfNominatorsReached(from view: ControllerBackedProtocol?, locale: Locale?) { + func presentMaxNumberOfNominatorsReached( + from view: ControllerBackedProtocol?, + stakingType: String, + locale: Locale? + ) { let message = R.string.localizable.stakingMaxNominatorsReachedMessage( preferredLanguages: locale?.rLanguages ) - let title = R.string.localizable.stakingMaxNominatorsReachedTitle( + let title = R.string.localizable.stakingIsNotAvailableTitle( + stakingType, preferredLanguages: locale?.rLanguages ) let closeAction = R.string.localizable.commonClose(preferredLanguages: locale?.rLanguages) diff --git a/novawallet/Modules/Staking/SelectValidatorsFlow/ValidatorSearch/ValidatorSearchViewController.swift b/novawallet/Modules/Staking/SelectValidatorsFlow/ValidatorSearch/ValidatorSearchViewController.swift index efa50336a1..10c233d92f 100644 --- a/novawallet/Modules/Staking/SelectValidatorsFlow/ValidatorSearch/ValidatorSearchViewController.swift +++ b/novawallet/Modules/Staking/SelectValidatorsFlow/ValidatorSearch/ValidatorSearchViewController.swift @@ -133,7 +133,7 @@ extension ValidatorSearchViewController: UITableViewDelegate { func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat { guard viewModel?.headerViewModel != nil else { return 0 } - return 26.0 + return 29 } func tableView(_ tableView: UITableView, viewForHeaderInSection _: Int) -> UIView? { @@ -173,7 +173,7 @@ extension ValidatorSearchViewController: EmptyStateDataSource { } var contentViewForEmptyState: UIView { - rootView.emptyStateContainer + rootView.contentView } var verticalSpacingForEmptyState: CGFloat? { diff --git a/novawallet/Modules/Staking/Services/EraValidatorsService/Model/NominationPoolModel.swift b/novawallet/Modules/Staking/Services/EraValidatorsService/Model/NominationPoolModel.swift index fc9e62c48f..032c1b8ac1 100644 --- a/novawallet/Modules/Staking/Services/EraValidatorsService/Model/NominationPoolModel.swift +++ b/novawallet/Modules/Staking/Services/EraValidatorsService/Model/NominationPoolModel.swift @@ -28,7 +28,7 @@ extension NominationPools { let state: PoolState? } - struct SelectedPool { + struct SelectedPool: Equatable { let poolId: PoolId let bondedAccountId: AccountId let metadata: Data? diff --git a/novawallet/Modules/Staking/StakingType/StakingTypePresenter.swift b/novawallet/Modules/Staking/StakingType/StakingTypePresenter.swift index b41b321d79..94dcdd768e 100644 --- a/novawallet/Modules/Staking/StakingType/StakingTypePresenter.swift +++ b/novawallet/Modules/Staking/StakingType/StakingTypePresenter.swift @@ -17,8 +17,8 @@ final class StakingTypePresenter { private var directStakingRestrictions: RelaychainStakingRestrictions? private var directStakingAvailable: Bool = false private var method: StakingSelectionMethod? + private var initialMethod: StakingSelectionMethod private var selection: StakingTypeSelection - private var hasChanges: Bool = false init( interactor: StakingTypeInteractorInputProtocol, @@ -38,6 +38,7 @@ final class StakingTypePresenter { self.delegate = delegate self.amount = amount self.canChangeType = canChangeType + self.initialMethod = initialMethod method = initialMethod switch initialMethod.selectedStakingOption { @@ -101,7 +102,10 @@ final class StakingTypePresenter { provideDirectStakingViewModel() provideNominationPoolViewModel() provideStakingSelection() + provideSaveChangesState() + } + private func provideSaveChangesState() { if hasChanges, method != nil { view?.didReceiveSaveChangesState(available: true) } else { @@ -184,6 +188,10 @@ final class StakingTypePresenter { wireframe.present(viewModel: viewModel, style: .alert, from: view) } + + private var hasChanges: Bool { + initialMethod.selectedStakingOption != method?.selectedStakingOption + } } extension StakingTypePresenter: StakingTypePresenterProtocol { @@ -265,6 +273,7 @@ extension StakingTypePresenter: StakingTypePresenterProtocol { provideStakingSelection() provideNominationPoolViewModel() + provideSaveChangesState() } else { let minStake = viewModelFactory.minStake( minStake: restrictions.minRewardableStake ?? restrictions.minJoinStake, @@ -280,6 +289,7 @@ extension StakingTypePresenter: StakingTypePresenterProtocol { provideStakingSelection() provideDirectStakingViewModel() + provideSaveChangesState() } interactor.change(stakingTypeSelection: selection) @@ -294,7 +304,7 @@ extension StakingTypePresenter: StakingTypePresenterProtocol { } func back() { - if hasChanges { + if hasChanges, method != nil { showSaveChangesAlert() } else { wireframe.complete(from: view) @@ -316,7 +326,6 @@ extension StakingTypePresenter: StakingTypeInteractorOutputProtocol { func didReceive(method: StakingSelectionMethod) { self.method = method - hasChanges = true updateView() } diff --git a/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupPresenter.swift b/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupPresenter.swift index 6306c343da..a2a6b36880 100644 --- a/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupPresenter.swift +++ b/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupPresenter.swift @@ -11,7 +11,8 @@ final class StakingUnbondSetupPresenter { let dataValidatingFactory: StakingDataValidatingFactoryProtocol let logger: LoggerProtocol? - let assetInfo: AssetBalanceDisplayInfo + var assetInfo: AssetBalanceDisplayInfo { chainAsset.assetDisplayInfo } + let chainAsset: ChainAsset private var bonded: Decimal? private var balance: Decimal? @@ -28,14 +29,14 @@ final class StakingUnbondSetupPresenter { wireframe: StakingUnbondSetupWireframeProtocol, balanceViewModelFactory: BalanceViewModelFactoryProtocol, dataValidatingFactory: StakingDataValidatingFactoryProtocol, - assetInfo: AssetBalanceDisplayInfo, + chainAsset: ChainAsset, logger: LoggerProtocol? = nil ) { self.interactor = interactor self.wireframe = wireframe self.balanceViewModelFactory = balanceViewModelFactory self.dataValidatingFactory = dataValidatingFactory - self.assetInfo = assetInfo + self.chainAsset = chainAsset self.logger = logger } @@ -111,6 +112,23 @@ extension StakingUnbondSetupPresenter: StakingUnbondSetupPresenterProtocol { func proceed() { let locale = view?.localizationManager?.selectedLocale ?? Locale.current + + var unbondAmount = inputAmount + + let bondedAmountInPlank = bonded?.toSubstrateAmount( + precision: chainAsset.assetDisplayInfo.assetPrecision + ) + let minStakeInPlank = minimalBalance?.toSubstrateAmount( + precision: chainAsset.assetDisplayInfo.assetPrecision + ) + + let minStakeValidationParams = MinStakeCrossedParams( + stakedAmountInPlank: bondedAmountInPlank, + minStake: minStakeInPlank + ) { [weak self] in + unbondAmount = self?.bonded + } + DataValidationRunner(validators: [ dataValidatingFactory.canUnbond(amount: inputAmount, bonded: bonded, locale: locale), @@ -126,14 +144,14 @@ extension StakingUnbondSetupPresenter: StakingUnbondSetupPresenterProtocol { locale: locale ), - dataValidatingFactory.stashIsNotKilledAfterUnbonding( - amount: inputAmount, - bonded: bonded, - minimumAmount: minimalBalance, + dataValidatingFactory.minStakeNotCrossed( + for: inputAmount ?? 0, + params: minStakeValidationParams, + chainAsset: chainAsset, locale: locale ) ]).runValidation { [weak self] in - if let amount = self?.inputAmount { + if let amount = unbondAmount { self?.wireframe.proceed(view: self?.view, amount: amount) } else { self?.logger?.warning("Missing amount after validation") diff --git a/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupViewFactory.swift b/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupViewFactory.swift index 84aaff504f..4e84c2e08b 100644 --- a/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupViewFactory.swift +++ b/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupViewFactory.swift @@ -22,14 +22,17 @@ struct StakingUnbondSetupViewFactory { priceAssetInfoFactory: priceAssetInfoFactory ) - let dataValidatingFactory = StakingDataValidatingFactory(presentable: wireframe) + let dataValidatingFactory = StakingDataValidatingFactory( + presentable: wireframe, + balanceFactory: balanceViewModelFactory + ) let presenter = StakingUnbondSetupPresenter( interactor: interactor, wireframe: wireframe, balanceViewModelFactory: balanceViewModelFactory, dataValidatingFactory: dataValidatingFactory, - assetInfo: assetInfo, + chainAsset: chainAsset, logger: Logger.shared ) diff --git a/novawallet/Modules/Staking/StartStakingConfirm/Presenter/StartStakingDirectConfirmPresenter.swift b/novawallet/Modules/Staking/StartStakingConfirm/Presenter/StartStakingDirectConfirmPresenter.swift index 3847a687bf..d3ed8965f6 100644 --- a/novawallet/Modules/Staking/StartStakingConfirm/Presenter/StartStakingDirectConfirmPresenter.swift +++ b/novawallet/Modules/Staking/StartStakingConfirm/Presenter/StartStakingDirectConfirmPresenter.swift @@ -84,4 +84,8 @@ final class StartStakingDirectConfirmPresenter: StartStakingConfirmPresenter { ) ] } + + override func stakingOption() -> SelectedStakingOption? { + .direct(model) + } } diff --git a/novawallet/Modules/Staking/StartStakingConfirm/Presenter/StartStakingPoolConfirmPresenter.swift b/novawallet/Modules/Staking/StartStakingConfirm/Presenter/StartStakingPoolConfirmPresenter.swift index a338cde8d2..7ba5ddea9c 100644 --- a/novawallet/Modules/Staking/StartStakingConfirm/Presenter/StartStakingPoolConfirmPresenter.swift +++ b/novawallet/Modules/Staking/StartStakingConfirm/Presenter/StartStakingPoolConfirmPresenter.swift @@ -66,4 +66,8 @@ final class StartStakingPoolConfirmPresenter: StartStakingConfirmPresenter { ) ] } + + override func stakingOption() -> SelectedStakingOption? { + .pool(model) + } } diff --git a/novawallet/Modules/Staking/StartStakingConfirm/StartStakingConfirmPresenter.swift b/novawallet/Modules/Staking/StartStakingConfirm/StartStakingConfirmPresenter.swift index 12d69c4eba..526be72ccc 100644 --- a/novawallet/Modules/Staking/StartStakingConfirm/StartStakingConfirmPresenter.swift +++ b/novawallet/Modules/Staking/StartStakingConfirm/StartStakingConfirmPresenter.swift @@ -111,6 +111,10 @@ class StartStakingConfirmPresenter { fatalError("Must be overriden by subsclass") } + func stakingOption() -> SelectedStakingOption? { + fatalError("Must be overriden by subsclass") + } + private func updateView() { provideAmountViewModel() provideWalletViewModel() @@ -144,6 +148,7 @@ class StartStakingConfirmPresenter { ), dataValidatingFactory.allowsNewNominators( flag: restrictions?.allowsNewStakers ?? true, + staking: stakingOption(), locale: selectedLocale ) ] diff --git a/novawallet/Modules/Staking/Validation/MinStakeCrossedParams.swift b/novawallet/Modules/Staking/Validation/MinStakeCrossedParams.swift new file mode 100644 index 0000000000..7c3c61150d --- /dev/null +++ b/novawallet/Modules/Staking/Validation/MinStakeCrossedParams.swift @@ -0,0 +1,7 @@ +import BigInt + +struct MinStakeCrossedParams { + let stakedAmountInPlank: BigUInt? + let minStake: BigUInt? + let unstakeAllHandler: () -> Void +} diff --git a/novawallet/Modules/Staking/Validation/NominationPoolDataValidatorFactory.swift b/novawallet/Modules/Staking/Validation/NominationPoolDataValidatorFactory.swift index a81185634f..6c4d7da9db 100644 --- a/novawallet/Modules/Staking/Validation/NominationPoolDataValidatorFactory.swift +++ b/novawallet/Modules/Staking/Validation/NominationPoolDataValidatorFactory.swift @@ -9,13 +9,7 @@ struct ExistentialDepositValidationParams { let amountUpdateClosure: (Decimal) -> Void } -struct MinStakeCrossedParams { - let stakedAmountInPlank: BigUInt? - let minStake: BigUInt? - let unstakeAllHandler: () -> Void -} - -protocol NominationPoolDataValidatorFactoryProtocol: BaseDataValidatingFactoryProtocol { +protocol NominationPoolDataValidatorFactoryProtocol: StakingBaseDataValidatingFactoryProtocol { func nominationPoolHasApy( pool: NominationPools.SelectedPool, locale: Locale @@ -46,13 +40,6 @@ protocol NominationPoolDataValidatorFactoryProtocol: BaseDataValidatingFactoryPr locale: Locale ) -> DataValidating - func minStakeNotCrossed( - for inputAmount: Decimal, - params: MinStakeCrossedParams, - chainAsset: ChainAsset, - locale: Locale - ) -> DataValidating - func canUnstake( for inputAmount: Decimal, stakedAmountInPlank: BigUInt?, @@ -84,16 +71,13 @@ protocol NominationPoolDataValidatorFactoryProtocol: BaseDataValidatingFactoryPr ) -> DataValidating } -final class NominationPoolDataValidatorFactory { - weak var view: (ControllerBackedProtocol & Localizable)? - let presentable: NominationPoolErrorPresentable - var basePresentable: BaseErrorPresentable { presentable } - - let balanceFactory: BalanceViewModelFactoryProtocol +final class NominationPoolDataValidatorFactory: StakingBaseDataValidatingFactory { + private let presentable: NominationPoolErrorPresentable init(presentable: NominationPoolErrorPresentable, balanceFactory: BalanceViewModelFactoryProtocol) { self.presentable = presentable - self.balanceFactory = balanceFactory + + super.init(presentable: presentable, balanceFactory: balanceFactory) } } @@ -275,58 +259,6 @@ extension NominationPoolDataValidatorFactory: NominationPoolDataValidatorFactory }) } - func minStakeNotCrossed( - for inputAmount: Decimal, - params: MinStakeCrossedParams, - chainAsset: ChainAsset, - locale: Locale - ) -> DataValidating { - let inputAmountInPlank = inputAmount.toSubstrateAmount( - precision: chainAsset.assetDisplayInfo.assetPrecision - ) ?? 0 - - let stakedAmountInPlank = params.stakedAmountInPlank - let minStake = params.minStake - - return WarningConditionViolation(onWarning: { [weak self] delegate in - guard let balanceFactory = self?.balanceFactory else { - return - } - - let stakedAmount = stakedAmountInPlank ?? 0 - let diff = stakedAmount >= inputAmountInPlank ? stakedAmount - inputAmountInPlank : 0 - - let minStakeDecimal = (minStake ?? 0).decimal(precision: chainAsset.asset.precision) - let diffDecimal = diff.decimal(precision: chainAsset.asset.precision) - - let minStakeString = balanceFactory.amountFromValue(minStakeDecimal).value(for: locale) - let diffString = balanceFactory.amountFromValue(diffDecimal).value(for: locale) - - self?.presentable.presentCrossedMinStake( - from: self?.view, - minStake: minStakeString, - remaining: diffString, - action: { - params.unstakeAllHandler() - delegate.didCompleteWarningHandling() - }, - locale: locale - ) - - }, preservesCondition: { - guard - let stakedAmountInPlank = stakedAmountInPlank, - let minStake = minStake, - stakedAmountInPlank >= inputAmountInPlank else { - return false - } - - let diff = stakedAmountInPlank - inputAmountInPlank - - return diff == 0 || diff >= minStake - }) - } - func canUnstake( for inputAmount: Decimal, stakedAmountInPlank: BigUInt?, diff --git a/novawallet/Modules/Staking/Validation/RelaychainStakingValidatorFacade.swift b/novawallet/Modules/Staking/Validation/RelaychainStakingValidatorFacade.swift index f9b87475c0..f2c33b3b20 100644 --- a/novawallet/Modules/Staking/Validation/RelaychainStakingValidatorFacade.swift +++ b/novawallet/Modules/Staking/Validation/RelaychainStakingValidatorFacade.swift @@ -56,6 +56,7 @@ final class RelaychainStakingValidatorFacade { private func createCommonValidations( params: RelaychainStakingValidationParams, restrictions: RelaychainStakingRestrictions?, + staking: SelectedStakingOption?, locale: Locale ) -> [DataValidating] { let assetDisplayInfo = params.chainAsset.assetDisplayInfo @@ -89,6 +90,7 @@ final class RelaychainStakingValidatorFacade { ), directStakingValidatingFactory.allowsNewNominators( flag: restrictions?.allowsNewStakers ?? false, + staking: staking, locale: locale ), directStakingValidatingFactory.canNominateInPlank( @@ -181,6 +183,7 @@ extension RelaychainStakingValidatorFacade: RelaychainStakingValidatorFacadeProt let commonValidations = createCommonValidations( params: params, restrictions: stakingMethod.restrictions, + staking: stakingMethod.selectedStakingOption, locale: locale ) diff --git a/novawallet/Modules/Staking/Validation/StakingBaseDataValidatingFactory.swift b/novawallet/Modules/Staking/Validation/StakingBaseDataValidatingFactory.swift new file mode 100644 index 0000000000..08eea3b35a --- /dev/null +++ b/novawallet/Modules/Staking/Validation/StakingBaseDataValidatingFactory.swift @@ -0,0 +1,77 @@ +import SoraFoundation + +protocol StakingBaseDataValidatingFactoryProtocol: BaseDataValidatingFactoryProtocol { + func minStakeNotCrossed( + for inputAmount: Decimal, + params: MinStakeCrossedParams, + chainAsset: ChainAsset, + locale: Locale + ) -> DataValidating +} + +class StakingBaseDataValidatingFactory: StakingBaseDataValidatingFactoryProtocol { + var view: (ControllerBackedProtocol & Localizable)? + var basePresentable: BaseErrorPresentable { presentable } + private let presentable: StakingBaseErrorPresentable + let balanceFactory: BalanceViewModelFactoryProtocol? + + init( + presentable: StakingBaseErrorPresentable, + balanceFactory: BalanceViewModelFactoryProtocol? + ) { + self.presentable = presentable + self.balanceFactory = balanceFactory + } + + func minStakeNotCrossed( + for inputAmount: Decimal, + params: MinStakeCrossedParams, + chainAsset: ChainAsset, + locale: Locale + ) -> DataValidating { + let inputAmountInPlank = inputAmount.toSubstrateAmount( + precision: chainAsset.assetDisplayInfo.assetPrecision + ) ?? 0 + + let stakedAmountInPlank = params.stakedAmountInPlank + let minStake = params.minStake + + return WarningConditionViolation(onWarning: { [weak self] delegate in + guard let balanceFactory = self?.balanceFactory else { + return + } + + let stakedAmount = stakedAmountInPlank ?? 0 + let diff = stakedAmount >= inputAmountInPlank ? stakedAmount - inputAmountInPlank : 0 + + let minStakeDecimal = (minStake ?? 0).decimal(precision: chainAsset.asset.precision) + let diffDecimal = diff.decimal(precision: chainAsset.asset.precision) + + let minStakeString = balanceFactory.amountFromValue(minStakeDecimal).value(for: locale) + let diffString = balanceFactory.amountFromValue(diffDecimal).value(for: locale) + + self?.presentable.presentCrossedMinStake( + from: self?.view, + minStake: minStakeString, + remaining: diffString, + action: { + params.unstakeAllHandler() + delegate.didCompleteWarningHandling() + }, + locale: locale + ) + + }, preservesCondition: { + guard + let stakedAmountInPlank = stakedAmountInPlank, + let minStake = minStake, + stakedAmountInPlank >= inputAmountInPlank else { + return false + } + + let diff = stakedAmountInPlank - inputAmountInPlank + + return diff == 0 || diff >= minStake + }) + } +} diff --git a/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift b/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift index d44a745b38..92bdab9692 100644 --- a/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift +++ b/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift @@ -9,7 +9,7 @@ struct MinStakeIsNotViolatedParams { let votersCount: UInt32? } -protocol StakingDataValidatingFactoryProtocol: BaseDataValidatingFactoryProtocol { +protocol StakingDataValidatingFactoryProtocol: StakingBaseDataValidatingFactoryProtocol { func canUnbond(amount: Decimal?, bonded: Decimal?, locale: Locale) -> DataValidating func canRebond(amount: Decimal?, unbonding: Decimal?, locale: Locale) -> DataValidating @@ -41,13 +41,6 @@ protocol StakingDataValidatingFactoryProtocol: BaseDataValidatingFactoryProtocol locale: Locale ) -> DataValidating - func stashIsNotKilledAfterUnbonding( - amount: Decimal?, - bonded: Decimal?, - minimumAmount: Decimal?, - locale: Locale - ) -> DataValidating - func ledgerNotExist( stakingLedger: StakingLedger?, locale: Locale @@ -76,21 +69,22 @@ protocol StakingDataValidatingFactoryProtocol: BaseDataValidatingFactoryProtocol locale: Locale ) -> DataValidating - func allowsNewNominators(flag: Bool, locale: Locale) -> DataValidating + func allowsNewNominators( + flag: Bool, + staking: SelectedStakingOption?, + locale: Locale + ) -> DataValidating } -final class StakingDataValidatingFactory { - weak var view: (ControllerBackedProtocol & Localizable)? - - var basePresentable: BaseErrorPresentable { presentable } - - let presentable: StakingErrorPresentable - - let balanceFactory: BalanceViewModelFactoryProtocol? +final class StakingDataValidatingFactory: StakingBaseDataValidatingFactory { + private let presentable: StakingErrorPresentable init(presentable: StakingErrorPresentable, balanceFactory: BalanceViewModelFactoryProtocol? = nil) { self.presentable = presentable - self.balanceFactory = balanceFactory + super.init( + presentable: presentable, + balanceFactory: balanceFactory + ) } } @@ -219,29 +213,6 @@ extension StakingDataValidatingFactory: StakingDataValidatingFactoryProtocol { }) } - func stashIsNotKilledAfterUnbonding( - amount: Decimal?, - bonded: Decimal?, - minimumAmount: Decimal?, - locale: Locale - ) -> DataValidating { - WarningConditionViolation(onWarning: { [weak self] delegate in - guard let view = self?.view else { - return - } - - self?.presentable.presentStashKilledAfterUnbond(from: view, action: { - delegate.didCompleteWarningHandling() - }, locale: locale) - }, preservesCondition: { - if let amount = amount, let bonded = bonded, let minimumAmount = minimumAmount { - return bonded - amount >= minimumAmount - } else { - return false - } - }) - } - func hasRedeemable(stakingLedger: StakingLedger?, in era: UInt32?, locale: Locale) -> DataValidating { ErrorConditionViolation(onError: { [weak self] in guard let view = self?.view else { @@ -321,7 +292,12 @@ extension StakingDataValidatingFactory: StakingDataValidatingFactoryProtocol { return } - self?.presentable.presentMaxNumberOfNominatorsReached(from: view, locale: locale) + let stakingType = R.string.localizable.stakingTypeDirect(preferredLanguages: locale.rLanguages) + self?.presentable.presentMaxNumberOfNominatorsReached( + from: view, + stakingType: stakingType, + locale: locale + ) }, preservesCondition: { if !hasExistingNomination, @@ -422,13 +398,33 @@ extension StakingDataValidatingFactory: StakingDataValidatingFactoryProtocol { }) } - func allowsNewNominators(flag: Bool, locale: Locale) -> DataValidating { + func allowsNewNominators( + flag: Bool, + staking: SelectedStakingOption?, + locale: Locale + ) -> DataValidating { ErrorConditionViolation(onError: { [weak self] in guard let view = self?.view else { return } - self?.presentable.presentMaxNumberOfNominatorsReached(from: view, locale: locale) + let stakingType: String + switch staking { + case .direct: + stakingType = R.string.localizable.stakingTypeDirect( + preferredLanguages: locale.rLanguages) + case .pool: + stakingType = R.string.localizable.stakingTypeNominationPool( + preferredLanguages: locale.rLanguages) + case .none: + stakingType = "" + } + + self?.presentable.presentMaxNumberOfNominatorsReached( + from: view, + stakingType: stakingType, + locale: locale + ) }, preservesCondition: { flag }) diff --git a/novawallet/Modules/Vote/Crowdloan/CrowdloanYourContributions/CrowdloanYourContributionsInteractor.swift b/novawallet/Modules/Vote/Crowdloan/CrowdloanYourContributions/CrowdloanYourContributionsInteractor.swift index 603032383e..741221da8e 100644 --- a/novawallet/Modules/Vote/Crowdloan/CrowdloanYourContributions/CrowdloanYourContributionsInteractor.swift +++ b/novawallet/Modules/Vote/Crowdloan/CrowdloanYourContributions/CrowdloanYourContributionsInteractor.swift @@ -2,7 +2,7 @@ import UIKit import RobinHood final class CrowdloanYourContributionsInteractor: RuntimeConstantFetching { - weak var presenter: CrowdloanYourContributionsInteractorOutputProtocol! + weak var presenter: CrowdloanYourContributionsInteractorOutputProtocol? let chain: ChainModel let selectedMetaAccount: MetaAccountModel @@ -48,7 +48,7 @@ final class CrowdloanYourContributionsInteractor: RuntimeConstantFetching { chain: chain ) } else { - presenter.didReceiveError(ChainAccountFetchingError.accountNotExists) + presenter?.didReceiveError(ChainAccountFetchingError.accountNotExists) } } @@ -56,7 +56,7 @@ final class CrowdloanYourContributionsInteractor: RuntimeConstantFetching { if let priceId = chain.utilityAsset()?.priceId { priceProvider = subscribeToPrice(for: priceId, currency: selectedCurrency) } else { - presenter.didReceivePrice(nil) + presenter?.didReceivePrice(nil) } } @@ -68,9 +68,9 @@ final class CrowdloanYourContributionsInteractor: RuntimeConstantFetching { ) { [weak self] (result: Result) in switch result { case let .success(blockTime): - self?.presenter.didReceiveBlockDuration(blockTime) + self?.presenter?.didReceiveBlockDuration(blockTime) case let .failure(error): - self?.presenter.didReceiveError(error) + self?.presenter?.didReceiveError(error) } } @@ -81,9 +81,9 @@ final class CrowdloanYourContributionsInteractor: RuntimeConstantFetching { ) { [weak self] (result: Result) in switch result { case let .success(period): - self?.presenter.didReceiveLeasingPeriod(period) + self?.presenter?.didReceiveLeasingPeriod(period) case let .failure(error): - self?.presenter.didReceiveError(error) + self?.presenter?.didReceiveError(error) } } @@ -94,9 +94,9 @@ final class CrowdloanYourContributionsInteractor: RuntimeConstantFetching { ) { [weak self] (result: Result) in switch result { case let .success(offset): - self?.presenter.didReceiveLeasingOffset(offset) + self?.presenter?.didReceiveLeasingOffset(offset) case let .failure(error): - self?.presenter.didReceiveError(error) + self?.presenter?.didReceiveError(error) } } } @@ -107,9 +107,9 @@ extension CrowdloanYourContributionsInteractor: CrowdloanLocalStorageSubscriber, func handleBlockNumber(result: Result, chainId _: ChainModel.Id) { switch result { case let .success(blockNumber): - presenter.didReceiveBlockNumber(blockNumber) + presenter?.didReceiveBlockNumber(blockNumber) case let .failure(error): - presenter.didReceiveError(error) + presenter?.didReceiveError(error) } } } @@ -131,9 +131,9 @@ extension CrowdloanYourContributionsInteractor: CrowdloanOffchainSubscriber, Cro ) { switch result { case let .success(maybeContributions): - presenter.didReceiveExternalContributions(maybeContributions ?? []) + presenter?.didReceiveExternalContributions(maybeContributions ?? []) case let .failure(error): - presenter.didReceiveError(error) + presenter?.didReceiveError(error) } } } @@ -142,9 +142,9 @@ extension CrowdloanYourContributionsInteractor: PriceLocalStorageSubscriber, Pri func handlePrice(result: Result, priceId _: AssetModel.PriceId) { switch result { case let .success(priceData): - presenter.didReceivePrice(priceData) + presenter?.didReceivePrice(priceData) case let .failure(error): - presenter.didReceiveError(error) + presenter?.didReceiveError(error) } } } diff --git a/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaContributionSource.swift b/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaContributionSource.swift index 838d2ee047..b312985978 100644 --- a/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaContributionSource.swift +++ b/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaContributionSource.swift @@ -3,8 +3,6 @@ import RobinHood import BigInt final class AcalaContributionSource: ExternalContributionSourceProtocol { - static let baseUrl = URL(string: "https://crowdloan.aca-api.network")! - static let apiContribution = "/contribution" var sourceName: String { "Acala Liquid" } let paraIdOperationFactory: ParaIdOperationFactoryProtocol @@ -15,44 +13,10 @@ final class AcalaContributionSource: ExternalContributionSourceProtocol { self.acalaChainId = acalaChainId } - func getContributions(accountId: AccountId, chain: ChainModel) -> CompoundOperationWrapper<[ExternalContribution]> { - guard let accountAddress = try? accountId.toAddress(using: chain.chainFormat) else { - return CompoundOperationWrapper.createWithError(ChainAccountFetchingError.accountNotExists) - } - - let url = Self.baseUrl - .appendingPathComponent(Self.apiContribution) - .appendingPathComponent(accountAddress) - - let requestFactory = BlockNetworkRequestFactory { - var request = URLRequest(url: url) - request.httpMethod = HttpMethod.get.rawValue - return request - } - - let resultFactory = AnyNetworkResultFactory { data in - try JSONDecoder().decode(AcalaLiquidContributionResponse.self, from: data) - } - - let networkOperation = NetworkOperation(requestFactory: requestFactory, resultFactory: resultFactory) - - let paraIdWrapper = paraIdOperationFactory.createParaIdOperation(for: acalaChainId) - - let mergeOperation = ClosureOperation<[ExternalContribution]> { [sourceName] in - let response = try networkOperation.extractNoCancellableResultData() - let paraId = try paraIdWrapper.targetOperation.extractNoCancellableResultData() - - guard let amount = BigUInt(response.proxyAmount) else { - throw CrowdloanBonusServiceError.internalError - } - - return [ExternalContribution(source: sourceName, amount: amount, paraId: paraId)] - } - - let dependencies = [networkOperation] + paraIdWrapper.allOperations - mergeOperation.addDependency(networkOperation) - mergeOperation.addDependency(paraIdWrapper.targetOperation) - - return CompoundOperationWrapper(targetOperation: mergeOperation, dependencies: dependencies) + func getContributions( + accountId _: AccountId, + chain _: ChainModel + ) -> CompoundOperationWrapper<[ExternalContribution]> { + CompoundOperationWrapper.createWithResult([]) } } diff --git a/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaLiquidContributionResponse.swift b/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaLiquidContributionResponse.swift deleted file mode 100644 index 87348216fd..0000000000 --- a/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaLiquidContributionResponse.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -struct AcalaLiquidContributionResponse: Decodable { - let proxyAmount: String -} diff --git a/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/ParallelContributionSource.swift b/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/ParallelContributionSource.swift index 3556438b81..a761ac288a 100644 --- a/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/ParallelContributionSource.swift +++ b/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/ParallelContributionSource.swift @@ -2,35 +2,12 @@ import Foundation import RobinHood final class ParallelContributionSource: ExternalContributionSourceProtocol { - static let baseURL = URL(string: "https://auction-service-prod.parallel.fi/crowdloan/rewards")! var sourceName: String { "Parallel" } - func getContributions(accountId: AccountId, chain: ChainModel) -> CompoundOperationWrapper<[ExternalContribution]> { - guard let accountAddress = try? accountId.toAddress(using: chain.chainFormat) else { - return CompoundOperationWrapper.createWithError(ChainAccountFetchingError.accountNotExists) - } - - let url = Self.baseURL - .appendingPathComponent(chain.name.lowercased()) - .appendingPathComponent(accountAddress) - - let requestFactory = BlockNetworkRequestFactory { - var request = URLRequest(url: url) - request.httpMethod = HttpMethod.get.rawValue - return request - } - - let resultFactory = AnyNetworkResultFactory<[ExternalContribution]> { [sourceName] data in - let resultData = try JSONDecoder().decode( - [ParallelContributionResponse].self, - from: data - ) - - return resultData.map { ExternalContribution(source: sourceName, amount: $0.amount, paraId: $0.paraId) } - } - - let operation = NetworkOperation(requestFactory: requestFactory, resultFactory: resultFactory) - - return CompoundOperationWrapper(targetOperation: operation) + func getContributions( + accountId _: AccountId, + chain _: ChainModel + ) -> CompoundOperationWrapper<[ExternalContribution]> { + CompoundOperationWrapper.createWithResult([]) } } diff --git a/novawallet/Modules/Vote/Governance/Delegations/DelegateSearch/GovernanceDelegateSearchViewController.swift b/novawallet/Modules/Vote/Governance/Delegations/DelegateSearch/GovernanceDelegateSearchViewController.swift index a7c8833f74..843f52f076 100644 --- a/novawallet/Modules/Vote/Governance/Delegations/DelegateSearch/GovernanceDelegateSearchViewController.swift +++ b/novawallet/Modules/Vote/Governance/Delegations/DelegateSearch/GovernanceDelegateSearchViewController.swift @@ -97,7 +97,7 @@ extension GovernanceDelegateSearchViewController: UITableViewDelegate { func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat { guard headerViewModel != nil else { return 0 } - return 26.0 + return 29 } func tableView(_ tableView: UITableView, viewForHeaderInSection _: Int) -> UIView? { @@ -158,7 +158,7 @@ extension GovernanceDelegateSearchViewController: EmptyStateDataSource { } var contentViewForEmptyState: UIView { - rootView.emptyStateContainer + rootView.contentView } var verticalSpacingForEmptyState: CGFloat? { 0 } diff --git a/novawallet/Modules/Vote/Governance/ReferendumSearch/ReferendumSearchViewLayout.swift b/novawallet/Modules/Vote/Governance/ReferendumSearch/ReferendumSearchViewLayout.swift index 8ff6ac8138..8f9c59a83b 100644 --- a/novawallet/Modules/Vote/Governance/ReferendumSearch/ReferendumSearchViewLayout.swift +++ b/novawallet/Modules/Vote/Governance/ReferendumSearch/ReferendumSearchViewLayout.swift @@ -9,8 +9,8 @@ final class ReferendumSearchViewLayout: BaseTableSearchViewLayout { make.edges.equalToSuperview() } - addSubview(emptyStateContainer) - emptyStateContainer.snp.makeConstraints { make in + addSubview(contentView) + contentView.snp.makeConstraints { make in make.leading.trailing.bottom.equalTo(safeAreaLayoutGuide) make.top.equalToSuperview() } @@ -43,7 +43,6 @@ final class ReferendumSearchViewLayout: BaseTableSearchViewLayout { override func setupStyle() { backgroundColor = .clear tableView.backgroundColor = .clear - emptyStateContainer.backgroundColor = .clear cancelButton.isHidden = false cancelButton.contentInsets = .init(top: 0, left: 16, bottom: 0, right: 16) diff --git a/novawallet/Resources/currencies.json b/novawallet/Resources/currencies.json index 3d6ee8a0b0..e3f12fa117 100644 --- a/novawallet/Resources/currencies.json +++ b/novawallet/Resources/currencies.json @@ -217,14 +217,6 @@ "id": 24, "coingeckoId": "inr" }, - { - "code": "KDW", - "name": "Kuwaiti Dinar", - "category": "fiat", - "popular": false, - "id": 25, - "coingeckoId": "kdw" - }, { "code": "LKR", "name": "Sri Lankan Rupee", diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index c5b13b8f45..ea8d826ba8 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -225,7 +225,7 @@ "staking.recommended.custom.title" = "Choose custom validators "; "fee.not.yet.loaded.title" = "Fee calculation is in progress"; "fee.not.yet.loaded.message" = "Please wait until fee is calculated"; -"staking.setup.amount.too.low" = "You can\'t stake less than the minimal value (%@)"; +"staking.setup.amount.too.low" = "You can\'t stake less than the network minimum value (%@)"; "common.confirm.title" = "Confirmation"; "staking.stash.title" = "Wallet account"; "staking.setup.sent.message" = "Staking setup transaction sent"; @@ -303,8 +303,6 @@ "staking.your.nominated.format" = "Nominated: %@"; "staking.your.validators.changing.title" = "Your validators will change in the next era."; "staking.validator.info.nominators" = "%@ (max %@)"; -"staking.unbonding.all.title" = "Stop staking"; -"staking.unbonding.all.message" = "Remaining staking balance will drop under minimum value and also will be added to unstaking amount"; "staking.unbonding.hint" = "Your tokens will be available to redeem after the unstaking period."; "staking.unbond.payee.reset.message" = "The reward destination will be changed to your account to avoid a bonded remnant since staking will be stopped"; "staking.redeemable.format" = "Redeemable: %@"; @@ -414,8 +412,7 @@ "staking.custom.header.validators.title" = "Validators: %li of %li"; "staking.set.validators.title" = "Select validators to start staking"; "staking.set.validators.message" = "Validators are not selected"; -"staking.max.nominators.reached.title" = "Cannot start staking"; -"staking.max.nominators.reached.message" = "Maximum number of nominators has been reached"; +"staking.max.nominators.reached.message" = "The maximum number of nominators has been reached. Try again later"; "staking.custom.blocked.warning" = "This validator is not accepting nominations at this moment. Please, try again in the next era."; "staking.alert.start.next.era.message" = "Please wait for the next era to start."; "staking.custom.proceed.button.disabled.title" = "Select validators (max %li)"; @@ -668,7 +665,6 @@ "dapp.search.query.section" = "Search"; "dapp.search.app.section" = "DApps"; "wallet.list.empty.message" = "Your assets will appear here.\nMake sure the \"Hide zero balances\"\nfilter is turned off"; -"wallet.list.empty.action.title" = "Buy tokens"; "assets.manage.hide.zero.balances" = "Hide assets with zero balances"; "wallet.send.dead.recipient.commission.asset.title" = "Recipient is not able to accept transfer"; "wallet.send.dead.recipient.commission.asset.message" = "Your transfer will fail since the destination account does not have enough %@ to accept other token transfers"; @@ -1334,6 +1330,7 @@ "staking.claim.rewards" = "Claim rewards"; "common.recommended" = "Recommended"; "staking.setup.amount.direct.type.subtitle" = "Selected: %d (max %d)"; +"staking.is.not.available.title" = "%@ is currently unavailable"; "staking.locked.pool.violation.error" = "You have locked tokens on your balance due to %@. In order to continue you should enter less than %@ or more than %@. To stake another amount you should remove your %@ locks."; "staking.locked.pool.violation.title" = "You can\'t stake the specified amount"; "staking.pool.has.no.apy.message" = "The pool you have selected is inactive due to no validators selected or its stake is less than the minimum.\nAre you sure you want to proceed with the selected Pool?"; @@ -1360,7 +1357,7 @@ "staking.select.pool.members" = "members"; "staking.select.pool.count" = "active pools: %d"; "staking.select.pool.title" = "Select pool"; -"staking.search.pool.empty" = "No pool with entered name or pool ID were found. Make sure you entered correct data"; +"staking.search.pool.empty" = "No pool with entered name or pool\nID were found. Make sure you\nentered correct data"; "staking.start.balance.with.fiat" = "Available balance: %1$@ (%2$@)"; "staking.pool.is.not.open.message" = "You cannot join pool that is not open. Please, contact the pool owner."; "staking.pool.is.not.open.title" = "Pool is not open"; @@ -1371,8 +1368,13 @@ "common.time.period.after" = "after"; "common.time.period.every" = "every"; "staking.search.pool.placeholder" = "Search by name or pool ID"; -"staking.pool.is.full.title" = "Selected pool is full"; +"staking.pool.is.full.title" = "Pool is full"; "staking.pool.is.full.message" = "You cannot join selected pool since it reached maximum number of members"; "common.back" = "Back"; +"wallet.list.empty.action.title" = "Buy tokens"; "asset.operation.send.empty.state.message" = "You don’t have tokens to send.\nBuy or Receive tokens to your\naccount."; "governance.referendums.status.deciding" = "Deciding"; +"common.alert.external.link.disclaimer.title" = "Continue in browser?"; +"common.alert.external.link.disclaimer.message" = "To continue the purchase you will be redirected from Nova Wallet app to %@"; +"polkadot.staking.promotion.title" = "Boost your DOT 🚀"; +"polkadot.staking.promotion.message" = "Received your DOT back from crowdloans? Start staking your DOT today to get the maximum possible rewards!"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 5c00bc04f7..ee84d1cd86 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -303,8 +303,6 @@ "staking.your.nominated.format" = "Номинировано: %@"; "staking.your.validators.changing.title" = "Ваши валидаторы поменяются в следующую эру"; "staking.validator.info.nominators" = "%@ (макс. %@ )"; -"staking.unbonding.all.title" = "Остановить стейкинг"; -"staking.unbonding.all.message" = "Оставшийся баланс стейка будет меньше минимального значения, поэтому он добавлен к сумме вывода."; "staking.unbonding.hint" = "Ваши токены будут доступны после истечения периода вывода."; "staking.unbond.payee.reset.message" = "Назначение вознаграждений будет изменено на ваш аккаунт, чтобы избежать остатка в стейке, поскольку стейкинг будет остановлен."; "staking.redeemable.format" = "Можно забрать: %@"; @@ -414,8 +412,7 @@ "staking.custom.header.validators.title" = "Валидаторов: %li из %li"; "staking.set.validators.title" = "Выберите валидаторов для начала стейкинга"; "staking.set.validators.message" = "Валидаторы не выбраны"; -"staking.max.nominators.reached.title" = "Невозможно начать стейкинг"; -"staking.max.nominators.reached.message" = "Достигнуто максимальное количество номинаторов"; +"staking.max.nominators.reached.message" = "Достигнуто максимальное количество номинаторов. Пожалуйста, попробуйте позже"; "staking.custom.blocked.warning" = "Этот валидатор не принимает номинации в данный момент. Пожалуйста, попробуйте снова в следующую эру."; "staking.alert.start.next.era.message" = "Пожалуйста, дождитесь начала следующей эры."; "staking.custom.proceed.button.disabled.title" = "Выбрать валидаторов (макс. %li)"; @@ -667,8 +664,7 @@ "common.fee.retry.failed" = "Загрузка комиссии завершилась с ошибкой. Хотите повторить?"; "dapp.search.query.section" = "Поиск"; "dapp.search.app.section" = "DApps"; -"wallet.list.empty.message" = "Здесь появятся ваши активы.\nУбедитесь, что фильтр\n\"Скрыть активы с нулевым балансом\"\nотключен."; -"wallet.list.empty.action.title" = "Купить токены"; +"wallet.list.empty.message" = "Здесь появятся ваши активы.\nУбедитесь, что фильтр\n\"Скрыть нулевой баланс\" отключен."; "assets.manage.hide.zero.balances" = "Скрыть активы с нулевым балансом"; "wallet.send.dead.recipient.commission.asset.title" = "Получатель не может принять перевод"; "wallet.send.dead.recipient.commission.asset.message" = "Ваш перевод завершится ошибкой, так как у получателя недостаточно %@ для приема переводов в других токенах."; @@ -1334,6 +1330,7 @@ "staking.claim.rewards" = "Забрать награды"; "common.recommended" = "Рекомендовано"; "staking.setup.amount.direct.type.subtitle" = "Выбрано: %d (макс. %d)"; +"staking.is.not.available.title" = "%@ в настоящее время недоступен"; "staking.locked.pool.violation.error" = "Вы заблокировали токены на своем балансе используя %@. Чтобы продолжить, вам следует ввести меньше %@ или больше %@. Чтобы поставить еще одну сумму, вам следует разблокировать токены из %@."; "staking.locked.pool.violation.title" = "Вы не можете застейкать указанную сумму"; "staking.pool.has.no.apy.message" = "Выбранный вами пул неактивен, поскольку у него не выбраны валидаторы или его стейк меньше минимальной.\nВы уверены, что хотите продолжить с выбранным пулом?"; @@ -1360,7 +1357,7 @@ "staking.select.pool.members" = "участники"; "staking.select.pool.count" = "активные пулы: %d"; "staking.select.pool.title" = "Выбрать пул"; -"staking.search.pool.empty" = "Пул с введенным именем или номером не найден. Убедитесь, что вы ввели правильные данные"; +"staking.search.pool.empty" = "Пул с введенным именем или\nномером не найден. Убедитесь, что вы\nввели правильные данные"; "staking.start.balance.with.fiat" = "Доступный баланс: %1$@ (%2$@)"; "staking.pool.is.not.open.message" = "Вы не можете присоединиться к не открытому пулу. Пожалуйста, свяжитесь с владельцем пула."; "staking.pool.is.not.open.title" = "Пул не открыт"; @@ -1371,8 +1368,13 @@ "common.time.period.after" = "через"; "common.time.period.every" = "раз в"; "staking.search.pool.placeholder" = "Поиск по имени или ID"; -"staking.pool.is.full.title" = "Выбранный пул заполнен"; +"staking.pool.is.full.title" = "Пул заполнен"; "staking.pool.is.full.message" = "Вы не можете присоединиться к выбранному пулу так как в нём не осталось места"; "common.back" = "Назад"; +"wallet.list.empty.action.title" = "Купить токены"; "asset.operation.send.empty.state.message" = "У вас нет токенов для отправки.\nКупите или получите токены\nна свой аккаунт."; -"governance.referendums.status.deciding" = "На рассмотрении"; +"governance.referendums.status.deciding" = "Решение"; +"common.alert.external.link.disclaimer.title" = "Продолжить в браузере?"; +"common.alert.external.link.disclaimer.message" = "Для продолжения покупки вы будете перенаправлены из приложения Nova Wallet на сайт %@"; +"polkadot.staking.promotion.title" = "Максимизируйте\nнаграды от DOT 🚀"; +"polkadot.staking.promotion.message" = "Получили свои DOT из краудлоунов? Начните стейкать DOT уже сегодня, чтобы получить максимальные вознаграждения!"; diff --git a/novawalletTests/Helper/ChainModelGenerator.swift b/novawalletTests/Helper/ChainModelGenerator.swift index 7213c2ce3e..62c46341d7 100644 --- a/novawalletTests/Helper/ChainModelGenerator.swift +++ b/novawalletTests/Helper/ChainModelGenerator.swift @@ -361,9 +361,11 @@ enum ChainModelGenerator { icon: nil, instances: [ .init(chainId: chainId1, - contractAddress: "0xeFAeeE334F0Fd1712f9a8cc375f427D9Cdd40d73"), + contractAddress: "0xeFAeeE334F0Fd1712f9a8cc375f427D9Cdd40d73", + buyProviders: nil), .init(chainId: chainId2, - contractAddress: "0xB44a9B6905aF7c801311e8F4E76932ee959c663C") + contractAddress: "0xB44a9B6905aF7c801311e8F4E76932ee959c663C", + buyProviders: nil) ]) } } diff --git a/novawalletTests/Helper/RuntimeHelper.swift b/novawalletTests/Helper/RuntimeHelper.swift index 7d7ff17c99..3152f71764 100644 --- a/novawalletTests/Helper/RuntimeHelper.swift +++ b/novawalletTests/Helper/RuntimeHelper.swift @@ -50,7 +50,7 @@ final class RuntimeHelper { let data = try Data(contentsOf: url) let basisNodes = BasisNodes.allNodes( for: runtimeMetadataContainer.metadata, - customExtensions: DefaultExtrinsicExtension.coders + customExtensions: DefaultExtrinsicExtension.getCoders(for: runtimeMetadataContainer.metadata) ) let registry = try TypeRegistry .createFromTypesDefinition(data: data, @@ -98,7 +98,7 @@ final class RuntimeHelper { return try TypeRegistryCatalog.createFromSiDefinition( versioningData: networkData, runtimeMetadata: metadata, - customExtensions: DefaultExtrinsicExtension.coders, + customExtensions: DefaultExtrinsicExtension.getCoders(for: runtimeMetadataContainer.metadata), customTypeMapper: SiDataTypeMapper(), customNameMapper: ScaleInfoCamelCaseMapper() ) diff --git a/novawalletTests/Modules/Staking/StakingRebondSetup/StakingRebondSetupTests.swift b/novawalletTests/Modules/Staking/StakingRebondSetup/StakingRebondSetupTests.swift index ff881aa33e..9963a951d2 100644 --- a/novawalletTests/Modules/Staking/StakingRebondSetup/StakingRebondSetupTests.swift +++ b/novawalletTests/Modules/Staking/StakingRebondSetup/StakingRebondSetupTests.swift @@ -167,8 +167,10 @@ class StakingRebondSetupTests: XCTestCase { inputExpectation.fulfill() } - when(stub).didReceiveTransferable(viewModel: any()).then { _ in - transferableExpectation.fulfill() + when(stub).didReceiveTransferable(viewModel: any()).then { optViewModel in + if optViewModel != nil { + transferableExpectation.fulfill() + } } } diff --git a/novawalletTests/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupTests.swift b/novawalletTests/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupTests.swift index c6a1fe1224..4fb36fc882 100644 --- a/novawalletTests/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupTests.swift +++ b/novawalletTests/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupTests.swift @@ -142,7 +142,7 @@ class StakingUnbondSetupTests: XCTestCase { wireframe: wireframe, balanceViewModelFactory: balanceViewModelFactory, dataValidatingFactory: StakingDataValidatingFactory(presentable: wireframe), - assetInfo: chainAsset.assetDisplayInfo + chainAsset: chainAsset ) presenter.view = view