From 2002c22bb52864ba3ce43f3e748933c7793a44e5 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Mon, 25 Sep 2023 12:46:19 +0300 Subject: [PATCH 01/37] init --- .../BaseTableSearchViewLayout.swift | 43 ++++++++++++------- .../NominationPoolSearchViewController.swift | 4 +- ...ParaStkCollatorsSearchViewController.swift | 2 +- .../ValidatorSearchViewController.swift | 4 +- ...vernanceDelegateSearchViewController.swift | 4 +- .../ReferendumSearchViewLayout.swift | 5 +-- 6 files changed, 37 insertions(+), 25 deletions(-) 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/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewController.swift b/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewController.swift index 828a45a81a..b8ee15226d 100644 --- a/novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewController.swift +++ b/novawallet/Modules/Staking/NominationPoolSearch/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/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/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/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) From 9f23d5b8b71121bbe335efd990f6184357100b67 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Mon, 25 Sep 2023 22:55:56 +0300 Subject: [PATCH 02/37] added line breakdown to empty state message --- novawallet/en.lproj/Localizable.strings | 2 +- novawallet/ru.lproj/Localizable.strings | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index c5b13b8f45..e3e98bd0cb 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1360,7 +1360,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"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 5c00bc04f7..10be78c5ac 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1360,7 +1360,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" = "Пул не открыт"; From e00acce6d5327817b516281cf32c69c7d352c76c Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Wed, 27 Sep 2023 12:54:15 +0300 Subject: [PATCH 03/37] init --- novawallet.xcodeproj/project.pbxproj | 4 ++ .../iconsBuyProviders/Contents.json | 6 ++ .../iconBanxa.imageset/Banxa.pdf | Bin 0 -> 1656 bytes .../iconBanxa.imageset/Contents.json | 12 ++++ .../iconMercuryo.imageset/Contents.json | 12 ++++ .../iconMercuryo.imageset/White Icons.pdf | Bin 0 -> 3625 bytes .../iconTransak.imageset/Contents.json | 12 ++++ .../iconTransak.imageset/iconTransak.pdf | Bin 0 -> 3237 bytes .../Extension/Foundation/String+Helpers.swift | 9 +++ .../PurchaseProvider/BanxaProvider.swift | 62 ++++++++++++++++++ .../PurchaseAggregator+Default.swift | 3 +- 11 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 novawallet/Assets.xcassets/iconsBuyProviders/Contents.json create mode 100644 novawallet/Assets.xcassets/iconsBuyProviders/iconBanxa.imageset/Banxa.pdf create mode 100644 novawallet/Assets.xcassets/iconsBuyProviders/iconBanxa.imageset/Contents.json create mode 100644 novawallet/Assets.xcassets/iconsBuyProviders/iconMercuryo.imageset/Contents.json create mode 100644 novawallet/Assets.xcassets/iconsBuyProviders/iconMercuryo.imageset/White Icons.pdf create mode 100644 novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/Contents.json create mode 100644 novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/iconTransak.pdf create mode 100644 novawallet/Common/PurchaseProvider/BanxaProvider.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 2ac49fc6f5..d4b4c1085b 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -656,6 +656,7 @@ 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 */; }; 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 */; }; @@ -4615,6 +4616,7 @@ 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 = ""; }; 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 = ""; }; @@ -10656,6 +10658,7 @@ children = ( 8448336627FAAF780077FB55 /* TransakProvider.swift */, 888B8E94291D274B00AA78E9 /* MercuryoProvider.swift */, + 772540332AC41FF7002B3FD4 /* BanxaProvider.swift */, 8436EDE625895846004D9E97 /* PurchaseProvider.swift */, 8436EDEE25896722004D9E97 /* PurchaseAggregator+Default.swift */, 8488ECD6258CDCBC004591CC /* PurchaseCompletionHandler.swift */, @@ -20909,6 +20912,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 */, 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 0000000000000000000000000000000000000000..4766616055e9d3b6bcaacf2b6debd566ef88f002 GIT binary patch literal 1656 zcmZXVO^@3~42JLXE9SC5a>$6BZvsJpW;ZE{w#dfmE$G4ZDjNr0skNLU?XS;ES(dcf zIT+(dLk?e(qONXl-o9X0jA2W*|Lcpf_T@|a>Xm7`JN;|?nm50--Tmpa4S*XxHJpy^ z_Q5na?Z5k7yZQOGz5cNNw;Rkqh8>N|(q;2v-p$X#DOdF+8X(!T;*!NO+>iRE92Ah4LXmhjB&raPnvkTZ z_fVFQGKe6~f;y>#T)p)Unmi}i6&C`chFx?iRLN`)fQgt2OrxuTOU|~q5~D{VI5{e@ z;?n9UWD%T7&c`5u;jcob)PQJ{i|i#AoP#9cs0e-~1t1yM3hj&_lu9+MptxKJXFXs$nCE=A?dZ2}Y}@t@`6;1TiT77;poq2C`NK6ypGA?v7oW}?gr+JDXJIi!we(JjZG}w=SV%lIe zfBg4%Yn$8ceLI2Q+q?btb^pzN9QEn+ST-K9kIOE*i>FOH^k0v=cCb3nHj}&SzB{!Y zZq#9Rfw$Wun8mw2qSeS#G>^jO*ZmzrwW?raFZW#cUt!Bn;vYa(p7JA4DsY%DT!de5 zPuoL(|Jd}~vHfJo7IW6Bzc!~7k_DcYjxSxdn^8cVj}S9K&kx9>ZyuvSNZ1dkEQY7! zc7JG(2H%F}mm8$x+0Z|<&+W7Q-Scx&noZxItO_0z+v_(;j*}*( z7nO6oA2VmpoE<-U_2T8%VXo5@oOiq5|CpS6`qVvpHf^@&`g4kR@zsx;?YryuE&-lu z)&BZwbM|3cz1aMHvEH11_uQSlzWdjDKm9d@*^htD-+cJ``00z^mdT>)g>A_o+IyKIxKW$WufcR*?LJz%aeq0Eq4`b zH4i9TDb-w>OV2^!`njag31mGHZVWvTT$oEpK4+JS#A;_9EOCTaz)`nD5;?@$$F1-S zA7U=gK4mX92`{%En-*ptSl|NR<{T2=Hn@S+5wdPcsuYP3e1S1m){v8;SDFz*?qWof zEK`(GAWtz=+ar3ZvK>&>zBjAf+2>}tchgBw6Jc2KE=s(`*3%s{&aJha&0S7J(#@(T zk0T0yK86se29@XZ$mval8|4n3YCzwJv(GAEjt+&!C>c za|B_nq&iZ{Fj;#Xec3ZivCigDf~Wov1HaULF_%i?CMAzJ3~^GhMg^0n(WziAq)yPD zq8oe5Tyjn{I-r?ls*s5~rq0$rS4iOAJO_HP3P4IuxPe3vcK>DZunO~{8h;%wQ_mbNMq zk{XgjqJgn|-MhG~oe2g1M7EbcuyRG7{L zCAvKHAGS6+8|{nMfJbUwgKFpkD(2ZBD9Y{yaX-=?x007k9k4?CvX%E~crXqqGuGC7 z5L+8Mpz{LW!2d<6qL{P@-A|Zwxp+Zdg_}9=2t-Ng_J2D5nK0d59G(m!T>m zszizCScyUB;f3AstYx<$4!-?@k;-7pY`o-zS?AbkcEet=QYIF>zF=z13}OnGjp&&k z4wmz9s#pk6V&ekr0>$tm=Q9-4%$G#DD9n6e(G0?dNGrCfS)6?QJFAa7c?<%Q~W zId-5y#rQ-Ap$K$VX*Fh-L^p8-9n4!R6VlKVWIg20Fx6yuLG3~`!z3casSVL%#F#o} z2qBG3?^8QoG~9%_LM@D3jcya=B%WwR`D(=zP$J*+IO4Rl2N+6Ca!IBU+EjADJ9t5d zpJ8obc_dLe0~{cH(|5f5yzz-w3%AciSQOnGGuw#Te@f zFNz4Jmb|+3Duu&bF**t20b646?N`%w`eo8oe;CP2T+uNT5t8cd3 z-SytR`Hi0k{9FC{&z~E&dUf`0a{zwYoL`(h-+gj#ZijsOYZ&@Qj2gvE+xRc%Xdb8z5{rb28U!7fnxpIK+{6-OU{Oz*(;o_W7oiaFeH}{cj}V^q zIm8q45W=tSt>VqS57CN8==$pH;&O8}@ndfF{R`-Lyx)D;JaLaN&Yvu#tWI~kYo~+< d!K>F7|89tW{HQy-x*q8;+^6Z$qi=tH`5zn>-ZcOK literal 0 HcmV?d00001 diff --git a/novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/Contents.json b/novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/Contents.json new file mode 100644 index 0000000000..4087a575ca --- /dev/null +++ b/novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "iconTransak.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/iconTransak.pdf b/novawallet/Assets.xcassets/iconsBuyProviders/iconTransak.imageset/iconTransak.pdf new file mode 100644 index 0000000000000000000000000000000000000000..07cb0aa3557e9b90b64d0432d0cfedcd8e062f6b GIT binary patch literal 3237 zcmeHJOK%%D5WerP;3Ys(AQXojzJS0${6x_PajhN#1U<;|#&&9JUG2JX^6UEzceN{7 z37X!!2b=oHd3`fO&WrQo)7PY9Rgp5Px%^xy_2!M5{r=^lR zyHOSgF9zsr-8?>Q36$UcUXkeq=g_xatlEdgx?U~6?Cc*L465djmt8S>+N|#f?4oX) z$Mv#q#l(2>@UVGOAC=LjeAB}62AWoXKoun0Ze1_#tJ!h=_wBO2I6G8F^X*^Dw)(50 zjrgeg^|;#KW1W-pp49Swk{pkXi*~uHyiU<4>l8W7(NoIm9w4U}Y!J}0wb?4KE#(}% zBC8`MA9)9|A;+B65~9O7uMImxUfoqzJD-vx6?L{IBnC!0QOLw9X@(_()alGAJF|_j ziH2N}^EO84N7&>z8v$K%E*LdIL3{Epdgx*U#|b{>=-7*-*UoW-62xz#qW~{}rQn?i z;9Rs4C$lv^31Ynq4v041W;Q9hzqM~fMzRlam;`v$WfMvkU*zR!dfawP)T8t zHXcolmY3#7?-yybDv54j&Y>ZT?P%Ex4@eWb1l5CP2xtcbTDLIx5)pKdS?O33isI}WFXjp0YT73(oZ9XfNe}U4+i>!08?gkDo6^(F%}3dqQDRudOo>~#RN&e zIFu*4SU{5)&IN5^aHc>3qVP?^e|GmRtDEZ6Pt~gWt*YwP)o?dsCGSrq?@Rv>ArD>- z6c|^-kG)U)|NA8GBAUHCk4E07alOBg_i03@ck^C)Yfvz)(B~sM3f)ce-@+z|JIVX^ z(k^N1({7y|vyQ8GE9=8NQU;C>xUwYE(_C=!)Sb<{MOT;o6Dt4a?-uQ!Wz~-~@^fbZ zndX$;)$B}3$qkdv?%v+78AhD9cMms^*QL|Hc%L}x{lR$k0GUxtsdj(gOL~x-t9v?N2 zkflBUTOeDS2tBr?N+N7Wi&NxBi*9k(T<^+0t?N$}u79ff9+ST+c$t!b51$y2PoYE0 zhtUHyDc=O)iF*cF!ri@l%39pX&mg}q81_#E3&P|ZE!re=UiB2uE}91X3VkJCv-$1UTDSnYZMRr=g&mt+s9wA{d4Kv3e*>>v literal 0 HcmV?d00001 diff --git a/novawallet/Common/Extension/Foundation/String+Helpers.swift b/novawallet/Common/Extension/Foundation/String+Helpers.swift index 49e05dcf6d..422da4c1e9 100644 --- a/novawallet/Common/Extension/Foundation/String+Helpers.swift +++ b/novawallet/Common/Extension/Foundation/String+Helpers.swift @@ -37,6 +37,15 @@ extension String { return self } } + + func without(prefix: String) -> String { + if hasPrefix(prefix) { + let indexStart = index(startIndex, offsetBy: prefix.count) + return String(self[indexStart...]) + } else { + return self + } + } } extension Optional where Wrapped == String { diff --git a/novawallet/Common/PurchaseProvider/BanxaProvider.swift b/novawallet/Common/PurchaseProvider/BanxaProvider.swift new file mode 100644 index 0000000000..1974ec6251 --- /dev/null +++ b/novawallet/Common/PurchaseProvider/BanxaProvider.swift @@ -0,0 +1,62 @@ +import Foundation +import CryptoKit +import SubstrateSdk + +final class BanxaProvider: PurchaseProviderProtocol { + #if F_RELEASE + let host = "" + #else + let host = "https://novawallet.banxa-sandbox.com" + #endif + + private var callbackUrl: URL? + private var colorCode: String? + + 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()!) + ] + } + + 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/PurchaseAggregator+Default.swift b/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift index c1406fa9de..5175595dd0 100644 --- a/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift +++ b/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift @@ -6,7 +6,8 @@ extension PurchaseAggregator { let purchaseProviders: [PurchaseProviderProtocol] = [ MercuryoProvider(), - TransakProvider() + TransakProvider(), + BanxaProvider() ] return PurchaseAggregator(providers: purchaseProviders) .with(appName: config.purchaseAppName) From 205debe4d6ae8e4fafa905e95fa1499f40ddf002 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Wed, 27 Sep 2023 12:54:59 +0300 Subject: [PATCH 04/37] fix icons --- .../iconMercuryo.imageset/Contents.json | 12 ------------ .../iconMercuryo.imageset/White Icons.pdf | Bin 3625 -> 0 bytes .../iconTransak.imageset/Contents.json | 12 ------------ .../iconTransak.imageset/iconTransak.pdf | Bin 3237 -> 0 bytes 4 files changed, 24 deletions(-) delete mode 100644 novawallet/Assets.xcassets/iconMercuryo.imageset/Contents.json delete mode 100644 novawallet/Assets.xcassets/iconMercuryo.imageset/White Icons.pdf delete mode 100644 novawallet/Assets.xcassets/iconTransak.imageset/Contents.json delete mode 100644 novawallet/Assets.xcassets/iconTransak.imageset/iconTransak.pdf diff --git a/novawallet/Assets.xcassets/iconMercuryo.imageset/Contents.json b/novawallet/Assets.xcassets/iconMercuryo.imageset/Contents.json deleted file mode 100644 index 832ab0de7e..0000000000 --- a/novawallet/Assets.xcassets/iconMercuryo.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "White Icons.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/novawallet/Assets.xcassets/iconMercuryo.imageset/White Icons.pdf b/novawallet/Assets.xcassets/iconMercuryo.imageset/White Icons.pdf deleted file mode 100644 index 3a3d652fa58664270db6c0926431d46ae39cf20d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3625 zcmZ{nU2mI35QXpGU$HlpS|ncY=j^UjRiY_H2oNRZuHqs%ZX1-^5T~f{>v_(;j*}*( z7nO6oA2VmpoE<-U_2T8%VXo5@oOiq5|CpS6`qVvpHf^@&`g4kR@zsx;?YryuE&-lu z)&BZwbM|3cz1aMHvEH11_uQSlzWdjDKm9d@*^htD-+cJ``00z^mdT>)g>A_o+IyKIxKW$WufcR*?LJz%aeq0Eq4`b zH4i9TDb-w>OV2^!`njag31mGHZVWvTT$oEpK4+JS#A;_9EOCTaz)`nD5;?@$$F1-S zA7U=gK4mX92`{%En-*ptSl|NR<{T2=Hn@S+5wdPcsuYP3e1S1m){v8;SDFz*?qWof zEK`(GAWtz=+ar3ZvK>&>zBjAf+2>}tchgBw6Jc2KE=s(`*3%s{&aJha&0S7J(#@(T zk0T0yK86se29@XZ$mval8|4n3YCzwJv(GAEjt+&!C>c za|B_nq&iZ{Fj;#Xec3ZivCigDf~Wov1HaULF_%i?CMAzJ3~^GhMg^0n(WziAq)yPD zq8oe5Tyjn{I-r?ls*s5~rq0$rS4iOAJO_HP3P4IuxPe3vcK>DZunO~{8h;%wQ_mbNMq zk{XgjqJgn|-MhG~oe2g1M7EbcuyRG7{L zCAvKHAGS6+8|{nMfJbUwgKFpkD(2ZBD9Y{yaX-=?x007k9k4?CvX%E~crXqqGuGC7 z5L+8Mpz{LW!2d<6qL{P@-A|Zwxp+Zdg_}9=2t-Ng_J2D5nK0d59G(m!T>m zszizCScyUB;f3AstYx<$4!-?@k;-7pY`o-zS?AbkcEet=QYIF>zF=z13}OnGjp&&k z4wmz9s#pk6V&ekr0>$tm=Q9-4%$G#DD9n6e(G0?dNGrCfS)6?QJFAa7c?<%Q~W zId-5y#rQ-Ap$K$VX*Fh-L^p8-9n4!R6VlKVWIg20Fx6yuLG3~`!z3casSVL%#F#o} z2qBG3?^8QoG~9%_LM@D3jcya=B%WwR`D(=zP$J*+IO4Rl2N+6Ca!IBU+EjADJ9t5d zpJ8obc_dLe0~{cH(|5f5yzz-w3%AciSQOnGGuw#Te@f zFNz4Jmb|+3Duu&bF**t20b646?N`%w`eo8oe;CP2T+uNT5t8cd3 z-SytR`Hi0k{9FC{&z~E&dUf`0a{zwYoL`(h-+gj#ZijsOYZ&@Qj2gvE+xRc%Xdb8z5{rb28U!7fnxpIK+{6-OU{Oz*(;o_W7oiaFeH}{cj}V^q zIm8q45W=tSt>VqS57CN8==$pH;&O8}@ndfF{R`-Lyx)D;JaLaN&Yvu#tWI~kYo~+< d!K>F7|89tW{HQy-x*q8;+^6Z$qi=tH`5zn>-ZcOK diff --git a/novawallet/Assets.xcassets/iconTransak.imageset/Contents.json b/novawallet/Assets.xcassets/iconTransak.imageset/Contents.json deleted file mode 100644 index 4087a575ca..0000000000 --- a/novawallet/Assets.xcassets/iconTransak.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "iconTransak.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/novawallet/Assets.xcassets/iconTransak.imageset/iconTransak.pdf b/novawallet/Assets.xcassets/iconTransak.imageset/iconTransak.pdf deleted file mode 100644 index 07cb0aa3557e9b90b64d0432d0cfedcd8e062f6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3237 zcmeHJOK%%D5WerP;3Ys(AQXojzJS0${6x_PajhN#1U<;|#&&9JUG2JX^6UEzceN{7 z37X!!2b=oHd3`fO&WrQo)7PY9Rgp5Px%^xy_2!M5{r=^lR zyHOSgF9zsr-8?>Q36$UcUXkeq=g_xatlEdgx?U~6?Cc*L465djmt8S>+N|#f?4oX) z$Mv#q#l(2>@UVGOAC=LjeAB}62AWoXKoun0Ze1_#tJ!h=_wBO2I6G8F^X*^Dw)(50 zjrgeg^|;#KW1W-pp49Swk{pkXi*~uHyiU<4>l8W7(NoIm9w4U}Y!J}0wb?4KE#(}% zBC8`MA9)9|A;+B65~9O7uMImxUfoqzJD-vx6?L{IBnC!0QOLw9X@(_()alGAJF|_j ziH2N}^EO84N7&>z8v$K%E*LdIL3{Epdgx*U#|b{>=-7*-*UoW-62xz#qW~{}rQn?i z;9Rs4C$lv^31Ynq4v041W;Q9hzqM~fMzRlam;`v$WfMvkU*zR!dfawP)T8t zHXcolmY3#7?-yybDv54j&Y>ZT?P%Ex4@eWb1l5CP2xtcbTDLIx5)pKdS?O33isI}WFXjp0YT73(oZ9XfNe}U4+i>!08?gkDo6^(F%}3dqQDRudOo>~#RN&e zIFu*4SU{5)&IN5^aHc>3qVP?^e|GmRtDEZ6Pt~gWt*YwP)o?dsCGSrq?@Rv>ArD>- z6c|^-kG)U)|NA8GBAUHCk4E07alOBg_i03@ck^C)Yfvz)(B~sM3f)ce-@+z|JIVX^ z(k^N1({7y|vyQ8GE9=8NQU;C>xUwYE(_C=!)Sb<{MOT;o6Dt4a?-uQ!Wz~-~@^fbZ zndX$;)$B}3$qkdv?%v+78AhD9cMms^*QL|Hc%L}x{lR$k0GUxtsdj(gOL~x-t9v?N2 zkflBUTOeDS2tBr?N+N7Wi&NxBi*9k(T<^+0t?N$}u79ff9+ST+c$t!b51$y2PoYE0 zhtUHyDc=O)iF*cF!ri@l%39pX&mg}q81_#E3&P|ZE!re=UiB2uE}91X3VkJCv-$1UTDSnYZMRr=g&mt+s9wA{d4Kv3e*>>v From 44fbf1c9faab22deb3786f9ab992741dcd707c5a Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Wed, 27 Sep 2023 16:25:39 +0300 Subject: [PATCH 05/37] move purchase logic to common protocols --- novawallet.xcodeproj/project.pbxproj | 8 +++ .../Common/Protocols/AlertPresentable.swift | 14 +++++ .../Common/Protocols/BuyPresentable.swift | 32 +++++++++++ .../Protocols/PurchasePresentable.swift | 48 +++++++++++++++++ .../AssetDetails/AssetDetailsPresenter.swift | 24 ++------- .../AssetDetails/AssetDetailsProtocols.swift | 13 +---- .../Buy/BuyAssetOperationPresenter.swift | 32 ++++------- .../Buy/BuyAssetOperationWireframe.swift | 54 +------------------ 8 files changed, 118 insertions(+), 107 deletions(-) create mode 100644 novawallet/Common/Protocols/BuyPresentable.swift create mode 100644 novawallet/Common/Protocols/PurchasePresentable.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index d4b4c1085b..19b78642f7 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -657,6 +657,8 @@ 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 /* BuyPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772540352AC45CDB002B3FD4 /* BuyPresentable.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 */; }; @@ -4617,6 +4619,8 @@ 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 /* BuyPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyPresentable.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 = ""; }; @@ -12980,6 +12984,8 @@ 8846F71F29D56A0700B8B776 /* Web3NameAddressListPresentable.swift */, 844C3E762A09228200C4305F /* WalletChoosePresentable.swift */, 0CC2E5692A6E6EBB004092E7 /* LocalStorageProviderObserving.swift */, + 772540352AC45CDB002B3FD4 /* BuyPresentable.swift */, + 772540372AC45D22002B3FD4 /* PurchasePresentable.swift */, ); path = Protocols; sourceTree = ""; @@ -21623,6 +21629,7 @@ 77F9FB072A9D96E900820625 /* NominationPoolBondMoreSetupPresenter.swift in Sources */, D1C6EABB48DC3EE254E5A095 /* CrowdloanContributionConfirmPresenter.swift in Sources */, 84BCC71C2924B69D00354DE0 /* ERC20SubscriptionManager.swift in Sources */, + 772540362AC45CDB002B3FD4 /* BuyPresentable.swift in Sources */, 843A2C7126A85B5B00266F53 /* GenericTitleValueView.swift in Sources */, 847F2D4F27AA695F00AFD476 /* AssetListGroupModel.swift in Sources */, 8499FED827BFCABD00712589 /* NftModel+Identifier.swift in Sources */, @@ -22670,6 +22677,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/Common/Protocols/AlertPresentable.swift b/novawallet/Common/Protocols/AlertPresentable.swift index 26bad01b26..b356fb3dc3 100644 --- a/novawallet/Common/Protocols/AlertPresentable.swift +++ b/novawallet/Common/Protocols/AlertPresentable.swift @@ -139,3 +139,17 @@ extension UIAlertController { presenter.present(alertView, animated: true, completion: nil) } } + +extension AlertPresentable { + 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/Protocols/BuyPresentable.swift b/novawallet/Common/Protocols/BuyPresentable.swift new file mode 100644 index 0000000000..f523e0235b --- /dev/null +++ b/novawallet/Common/Protocols/BuyPresentable.swift @@ -0,0 +1,32 @@ +protocol BuyPresentable { + func buyTokens( + from view: ControllerBackedProtocol?, + purchaseActions: [PurchaseAction], + wireframe: PurchasePresentable? + ) +} + +extension BuyPresentable where Self: ModalPickerViewControllerDelegate & PurchaseDelegate { + func buyTokens( + from view: ControllerBackedProtocol?, + purchaseActions: [PurchaseAction], + wireframe: PurchasePresentable? + ) { + 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 + ) + } + } +} diff --git a/novawallet/Common/Protocols/PurchasePresentable.swift b/novawallet/Common/Protocols/PurchasePresentable.swift new file mode 100644 index 0000000000..604331de1c --- /dev/null +++ b/novawallet/Common/Protocols/PurchasePresentable.swift @@ -0,0 +1,48 @@ +protocol PurchasePresentable { + func showPurchaseTokens( + from view: ControllerBackedProtocol?, + action: PurchaseAction, + delegate: PurchaseDelegate + ) + + func showPurchaseProviders( + from view: ControllerBackedProtocol?, + actions: [PurchaseAction], + delegate: ModalPickerViewControllerDelegate + ) +} + +extension PurchasePresentable where Self: 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 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) + } +} diff --git a/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift b/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift index de7d989fe2..5326530057 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: BuyPresentable { weak var view: AssetDetailsViewProtocol? let wireframe: AssetDetailsWireframeProtocol let viewModelFactory: AssetDetailsViewModelFactoryProtocol @@ -95,22 +95,7 @@ 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 - ) - } + buyTokens(from: view, purchaseActions: purchaseActions, wireframe: wireframe) } private func showReceiveTokens() { @@ -263,9 +248,6 @@ extension AssetDetailsPresenter: ModalPickerViewControllerDelegate { 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/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift b/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift index 64d371afda..5ce29b43aa 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 & BuyPresentable { 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,14 @@ final class BuyAssetOperationPresenter: AssetsSearchPresenter { on: view, by: commonCheckResult, successRouteClosure: { [weak self] in - self?.showPurchase() + guard let self = self else { + return + } + self.buyTokens( + from: self.view, + purchaseActions: self.purchaseActions, + wireframe: self.buyAssetWireframe + ) } ) case .noBuyOptions: @@ -91,9 +82,6 @@ extension BuyAssetOperationPresenter: ModalPickerViewControllerDelegate { 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?) { From 653f891979c060df6e4bebc257b330b67f957247 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Wed, 27 Sep 2023 16:49:39 +0300 Subject: [PATCH 06/37] added alert --- .../Common/Protocols/BuyPresentable.swift | 47 ++++++++++++++++--- .../AssetDetails/AssetDetailsPresenter.swift | 13 ++--- .../Buy/BuyAssetOperationPresenter.swift | 10 ++-- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/novawallet/Common/Protocols/BuyPresentable.swift b/novawallet/Common/Protocols/BuyPresentable.swift index f523e0235b..8f88412686 100644 --- a/novawallet/Common/Protocols/BuyPresentable.swift +++ b/novawallet/Common/Protocols/BuyPresentable.swift @@ -1,8 +1,11 @@ +import SoraFoundation + protocol BuyPresentable { func buyTokens( from view: ControllerBackedProtocol?, purchaseActions: [PurchaseAction], - wireframe: PurchasePresentable? + wireframe: (PurchasePresentable & AlertPresentable)?, + locale: Locale ) } @@ -10,17 +13,14 @@ extension BuyPresentable where Self: ModalPickerViewControllerDelegate & Purchas func buyTokens( from view: ControllerBackedProtocol?, purchaseActions: [PurchaseAction], - wireframe: PurchasePresentable? + wireframe: (PurchasePresentable & AlertPresentable)?, + locale: Locale ) { guard !purchaseActions.isEmpty else { return } if purchaseActions.count == 1 { - wireframe?.showPurchaseTokens( - from: view, - action: purchaseActions[0], - delegate: self - ) + buyTokens(from: view, purchaseAction: purchaseActions[0], wireframe: wireframe, locale: locale) } else { wireframe?.showPurchaseProviders( from: view, @@ -29,4 +29,37 @@ extension BuyPresentable where Self: ModalPickerViewControllerDelegate & Purchas ) } } + + func buyTokens( + from view: ControllerBackedProtocol?, + purchaseAction: PurchaseAction, + wireframe: (PurchasePresentable & AlertPresentable)?, + locale: Locale + ) { + let title = "You are leaving Nova Wallet" + let message = "You will be redirected to banxa.com" + + let closeTitle = R.string.localizable + .commonCancel(preferredLanguages: locale.rLanguages) + let proceedTitle = R.string.localizable + .commonProceed(preferredLanguages: locale.rLanguages) + let proceedAction = AlertPresentableAction(title: proceedTitle) { + wireframe?.showPurchaseTokens( + from: view, + action: purchaseAction, + delegate: self + ) + } + + wireframe?.present( + viewModel: .init( + title: title, + message: message, + actions: [proceedAction], + closeAction: closeTitle + ), + style: .alert, + from: view + ) + } } diff --git a/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift b/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift index 5326530057..78b45570ad 100644 --- a/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift +++ b/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift @@ -95,7 +95,12 @@ final class AssetDetailsPresenter: BuyPresentable { } private func showPurchase() { - buyTokens(from: view, purchaseActions: purchaseActions, wireframe: wireframe) + buyTokens( + from: view, + purchaseActions: purchaseActions, + wireframe: wireframe, + locale: selectedLocale + ) } private func showReceiveTokens() { @@ -238,11 +243,7 @@ extension AssetDetailsPresenter: Localizable { extension AssetDetailsPresenter: ModalPickerViewControllerDelegate { func modalPickerDidSelectModelAtIndex(_ index: Int, context _: AnyObject?) { - wireframe.showPurchaseTokens( - from: view, - action: purchaseActions[index], - delegate: self - ) + buyTokens(from: view, purchaseAction: purchaseActions[index], wireframe: wireframe, locale: selectedLocale) } } diff --git a/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift b/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift index 5ce29b43aa..e2da0d87e3 100644 --- a/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift +++ b/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift @@ -60,7 +60,8 @@ final class BuyAssetOperationPresenter: AssetsSearchPresenter & BuyPresentable { self.buyTokens( from: self.view, purchaseActions: self.purchaseActions, - wireframe: self.buyAssetWireframe + wireframe: self.buyAssetWireframe, + locale: selectedLocale ) } ) @@ -72,10 +73,11 @@ final class BuyAssetOperationPresenter: AssetsSearchPresenter & BuyPresentable { extension BuyAssetOperationPresenter: ModalPickerViewControllerDelegate { func modalPickerDidSelectModelAtIndex(_ index: Int, context _: AnyObject?) { - buyAssetWireframe?.showPurchaseTokens( + buyTokens( from: view, - action: purchaseActions[index], - delegate: self + purchaseAction: purchaseActions[index], + wireframe: buyAssetWireframe, + locale: selectedLocale ) } } From 8febdc51d7dde6001255f6e9a9f2469202795b4b Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Thu, 28 Sep 2023 09:43:37 +0300 Subject: [PATCH 07/37] clean up, add localization --- novawallet.xcodeproj/project.pbxproj | 8 +++--- ...resentable.swift => PurchaseHandler.swift} | 27 ++++++++++--------- .../PurchaseProvider/BanxaProvider.swift | 8 +++++- .../PurchaseProvider/MercuryoProvider.swift | 8 +++++- .../PurchaseProviderProtocol.swift | 1 + .../PurchaseProvider/TransakProvider.swift | 8 +++++- .../AssetDetails/AssetDetailsPresenter.swift | 6 ++--- .../Buy/BuyAssetOperationPresenter.swift | 6 ++--- novawallet/en.lproj/Localizable.strings | 2 ++ novawallet/ru.lproj/Localizable.strings | 2 ++ 10 files changed, 51 insertions(+), 25 deletions(-) rename novawallet/Common/Protocols/{BuyPresentable.swift => PurchaseHandler.swift} (62%) diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 19b78642f7..48f293579f 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -657,7 +657,7 @@ 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 /* BuyPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772540352AC45CDB002B3FD4 /* BuyPresentable.swift */; }; + 772540362AC45CDB002B3FD4 /* PurchaseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772540352AC45CDB002B3FD4 /* PurchaseHandler.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 */; }; @@ -4619,7 +4619,7 @@ 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 /* BuyPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyPresentable.swift; sourceTree = ""; }; + 772540352AC45CDB002B3FD4 /* PurchaseHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseHandler.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 = ""; }; @@ -12984,7 +12984,7 @@ 8846F71F29D56A0700B8B776 /* Web3NameAddressListPresentable.swift */, 844C3E762A09228200C4305F /* WalletChoosePresentable.swift */, 0CC2E5692A6E6EBB004092E7 /* LocalStorageProviderObserving.swift */, - 772540352AC45CDB002B3FD4 /* BuyPresentable.swift */, + 772540352AC45CDB002B3FD4 /* PurchaseHandler.swift */, 772540372AC45D22002B3FD4 /* PurchasePresentable.swift */, ); path = Protocols; @@ -21629,7 +21629,7 @@ 77F9FB072A9D96E900820625 /* NominationPoolBondMoreSetupPresenter.swift in Sources */, D1C6EABB48DC3EE254E5A095 /* CrowdloanContributionConfirmPresenter.swift in Sources */, 84BCC71C2924B69D00354DE0 /* ERC20SubscriptionManager.swift in Sources */, - 772540362AC45CDB002B3FD4 /* BuyPresentable.swift in Sources */, + 772540362AC45CDB002B3FD4 /* PurchaseHandler.swift in Sources */, 843A2C7126A85B5B00266F53 /* GenericTitleValueView.swift in Sources */, 847F2D4F27AA695F00AFD476 /* AssetListGroupModel.swift in Sources */, 8499FED827BFCABD00712589 /* NftModel+Identifier.swift in Sources */, diff --git a/novawallet/Common/Protocols/BuyPresentable.swift b/novawallet/Common/Protocols/PurchaseHandler.swift similarity index 62% rename from novawallet/Common/Protocols/BuyPresentable.swift rename to novawallet/Common/Protocols/PurchaseHandler.swift index 8f88412686..49cf17e7b9 100644 --- a/novawallet/Common/Protocols/BuyPresentable.swift +++ b/novawallet/Common/Protocols/PurchaseHandler.swift @@ -1,7 +1,7 @@ import SoraFoundation -protocol BuyPresentable { - func buyTokens( +protocol PurchaseHandler { + func handlePurchase( from view: ControllerBackedProtocol?, purchaseActions: [PurchaseAction], wireframe: (PurchasePresentable & AlertPresentable)?, @@ -9,8 +9,8 @@ protocol BuyPresentable { ) } -extension BuyPresentable where Self: ModalPickerViewControllerDelegate & PurchaseDelegate { - func buyTokens( +extension PurchaseHandler where Self: ModalPickerViewControllerDelegate & PurchaseDelegate { + func handlePurchase( from view: ControllerBackedProtocol?, purchaseActions: [PurchaseAction], wireframe: (PurchasePresentable & AlertPresentable)?, @@ -20,7 +20,7 @@ extension BuyPresentable where Self: ModalPickerViewControllerDelegate & Purchas return } if purchaseActions.count == 1 { - buyTokens(from: view, purchaseAction: purchaseActions[0], wireframe: wireframe, locale: locale) + handlePurchase(from: view, purchaseAction: purchaseActions[0], wireframe: wireframe, locale: locale) } else { wireframe?.showPurchaseProviders( from: view, @@ -30,20 +30,23 @@ extension BuyPresentable where Self: ModalPickerViewControllerDelegate & Purchas } } - func buyTokens( + func handlePurchase( from view: ControllerBackedProtocol?, purchaseAction: PurchaseAction, wireframe: (PurchasePresentable & AlertPresentable)?, locale: Locale ) { - let title = "You are leaving Nova Wallet" - let message = "You will be redirected to banxa.com" + 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 proceedTitle = R.string.localizable - .commonProceed(preferredLanguages: locale.rLanguages) - let proceedAction = AlertPresentableAction(title: proceedTitle) { + let continueTitle = R.string.localizable + .commonContinue(preferredLanguages: locale.rLanguages) + let continueAction = AlertPresentableAction(title: continueTitle) { wireframe?.showPurchaseTokens( from: view, action: purchaseAction, @@ -55,7 +58,7 @@ extension BuyPresentable where Self: ModalPickerViewControllerDelegate & Purchas viewModel: .init( title: title, message: message, - actions: [proceedAction], + actions: [continueAction], closeAction: closeTitle ), style: .alert, diff --git a/novawallet/Common/PurchaseProvider/BanxaProvider.swift b/novawallet/Common/PurchaseProvider/BanxaProvider.swift index 1974ec6251..926f752908 100644 --- a/novawallet/Common/PurchaseProvider/BanxaProvider.swift +++ b/novawallet/Common/PurchaseProvider/BanxaProvider.swift @@ -11,6 +11,7 @@ final class BanxaProvider: PurchaseProviderProtocol { private var callbackUrl: URL? private var colorCode: String? + private let displayURL = "banxa.com" func with(callbackUrl: URL) -> Self { self.callbackUrl = callbackUrl @@ -37,7 +38,12 @@ final class BanxaProvider: PurchaseProviderProtocol { } return [ - PurchaseAction(title: "Banxa", url: url, icon: R.image.iconBanxa()!) + PurchaseAction( + title: "Banxa", + url: url, + icon: R.image.iconBanxa()!, + displayURL: displayURL + ) ] } 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/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/Modules/AssetDetails/AssetDetailsPresenter.swift b/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift index 78b45570ad..3ecf8b9f13 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: BuyPresentable { +final class AssetDetailsPresenter: PurchaseHandler { weak var view: AssetDetailsViewProtocol? let wireframe: AssetDetailsWireframeProtocol let viewModelFactory: AssetDetailsViewModelFactoryProtocol @@ -95,7 +95,7 @@ final class AssetDetailsPresenter: BuyPresentable { } private func showPurchase() { - buyTokens( + handlePurchase( from: view, purchaseActions: purchaseActions, wireframe: wireframe, @@ -243,7 +243,7 @@ extension AssetDetailsPresenter: Localizable { extension AssetDetailsPresenter: ModalPickerViewControllerDelegate { func modalPickerDidSelectModelAtIndex(_ index: Int, context _: AnyObject?) { - buyTokens(from: view, purchaseAction: purchaseActions[index], wireframe: wireframe, locale: selectedLocale) + handlePurchase(from: view, purchaseAction: purchaseActions[index], wireframe: wireframe, locale: selectedLocale) } } diff --git a/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift b/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift index e2da0d87e3..8067e52747 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 & BuyPresentable { +final class BuyAssetOperationPresenter: AssetsSearchPresenter & PurchaseHandler { var buyAssetWireframe: BuyAssetOperationWireframeProtocol? { wireframe as? BuyAssetOperationWireframeProtocol } @@ -57,7 +57,7 @@ final class BuyAssetOperationPresenter: AssetsSearchPresenter & BuyPresentable { guard let self = self else { return } - self.buyTokens( + self.handlePurchase( from: self.view, purchaseActions: self.purchaseActions, wireframe: self.buyAssetWireframe, @@ -73,7 +73,7 @@ final class BuyAssetOperationPresenter: AssetsSearchPresenter & BuyPresentable { extension BuyAssetOperationPresenter: ModalPickerViewControllerDelegate { func modalPickerDidSelectModelAtIndex(_ index: Int, context _: AnyObject?) { - buyTokens( + handlePurchase( from: view, purchaseAction: purchaseActions[index], wireframe: buyAssetWireframe, diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index c5b13b8f45..f5a1b3ab93 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1376,3 +1376,5 @@ "common.back" = "Back"; "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" = "You are leaving Nova Wallet"; +"common.alert.external.link.disclaimer.message" = "You will be redirected to %@"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 5c00bc04f7..e9a2f04bd0 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1376,3 +1376,5 @@ "common.back" = "Назад"; "asset.operation.send.empty.state.message" = "У вас нет токенов для отправки.\nКупите или получите токены\nна свой аккаунт."; "governance.referendums.status.deciding" = "На рассмотрении"; +"common.alert.external.link.disclaimer.title" = "Вы покидаете Nova Wallet"; +"common.alert.external.link.disclaimer.message" = "Вы будете перенаправлены на сайт %@"; From 253808eca01512547b90c84209bda94da322a918 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Thu, 28 Sep 2023 09:46:48 +0300 Subject: [PATCH 08/37] cleanup --- .../Common/Extension/Foundation/String+Helpers.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/novawallet/Common/Extension/Foundation/String+Helpers.swift b/novawallet/Common/Extension/Foundation/String+Helpers.swift index 422da4c1e9..fdd0bc88d8 100644 --- a/novawallet/Common/Extension/Foundation/String+Helpers.swift +++ b/novawallet/Common/Extension/Foundation/String+Helpers.swift @@ -38,14 +38,6 @@ extension String { } } - func without(prefix: String) -> String { - if hasPrefix(prefix) { - let indexStart = index(startIndex, offsetBy: prefix.count) - return String(self[indexStart...]) - } else { - return self - } - } } extension Optional where Wrapped == String { From 2d7a1b7056953e732a43562180dc2b791a1c426c Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Thu, 28 Sep 2023 09:49:00 +0300 Subject: [PATCH 09/37] build formatting --- novawallet/Common/Extension/Foundation/String+Helpers.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/novawallet/Common/Extension/Foundation/String+Helpers.swift b/novawallet/Common/Extension/Foundation/String+Helpers.swift index fdd0bc88d8..49e05dcf6d 100644 --- a/novawallet/Common/Extension/Foundation/String+Helpers.swift +++ b/novawallet/Common/Extension/Foundation/String+Helpers.swift @@ -37,7 +37,6 @@ extension String { return self } } - } extension Optional where Wrapped == String { From ae8220f1e4d99b98b5ea49db5d8b121c0fbacfb5 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Thu, 28 Sep 2023 13:50:35 +0300 Subject: [PATCH 10/37] PR fixes --- novawallet.xcodeproj/project.pbxproj | 8 +++---- .../Common/Protocols/AlertPresentable.swift | 14 ------------- ...ndler.swift => PurchaseFlowManaging.swift} | 12 +++++------ .../Protocols/PurchasePresentable.swift | 21 ++++++++++++++++++- .../PurchaseProvider/BanxaProvider.swift | 1 + .../AssetDetails/AssetDetailsPresenter.swift | 11 +++++++--- .../Buy/BuyAssetOperationPresenter.swift | 6 +++--- 7 files changed, 42 insertions(+), 31 deletions(-) rename novawallet/Common/Protocols/{PurchaseHandler.swift => PurchaseFlowManaging.swift} (85%) diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 48f293579f..b6214f1f12 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -657,7 +657,7 @@ 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 /* PurchaseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772540352AC45CDB002B3FD4 /* PurchaseHandler.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 */; }; @@ -4619,7 +4619,7 @@ 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 /* PurchaseHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseHandler.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 = ""; }; @@ -12984,7 +12984,7 @@ 8846F71F29D56A0700B8B776 /* Web3NameAddressListPresentable.swift */, 844C3E762A09228200C4305F /* WalletChoosePresentable.swift */, 0CC2E5692A6E6EBB004092E7 /* LocalStorageProviderObserving.swift */, - 772540352AC45CDB002B3FD4 /* PurchaseHandler.swift */, + 772540352AC45CDB002B3FD4 /* PurchaseFlowManaging.swift */, 772540372AC45D22002B3FD4 /* PurchasePresentable.swift */, ); path = Protocols; @@ -21629,7 +21629,7 @@ 77F9FB072A9D96E900820625 /* NominationPoolBondMoreSetupPresenter.swift in Sources */, D1C6EABB48DC3EE254E5A095 /* CrowdloanContributionConfirmPresenter.swift in Sources */, 84BCC71C2924B69D00354DE0 /* ERC20SubscriptionManager.swift in Sources */, - 772540362AC45CDB002B3FD4 /* PurchaseHandler.swift in Sources */, + 772540362AC45CDB002B3FD4 /* PurchaseFlowManaging.swift in Sources */, 843A2C7126A85B5B00266F53 /* GenericTitleValueView.swift in Sources */, 847F2D4F27AA695F00AFD476 /* AssetListGroupModel.swift in Sources */, 8499FED827BFCABD00712589 /* NftModel+Identifier.swift in Sources */, diff --git a/novawallet/Common/Protocols/AlertPresentable.swift b/novawallet/Common/Protocols/AlertPresentable.swift index b356fb3dc3..26bad01b26 100644 --- a/novawallet/Common/Protocols/AlertPresentable.swift +++ b/novawallet/Common/Protocols/AlertPresentable.swift @@ -139,17 +139,3 @@ extension UIAlertController { presenter.present(alertView, animated: true, completion: nil) } } - -extension AlertPresentable { - 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/Protocols/PurchaseHandler.swift b/novawallet/Common/Protocols/PurchaseFlowManaging.swift similarity index 85% rename from novawallet/Common/Protocols/PurchaseHandler.swift rename to novawallet/Common/Protocols/PurchaseFlowManaging.swift index 49cf17e7b9..35348b3a48 100644 --- a/novawallet/Common/Protocols/PurchaseHandler.swift +++ b/novawallet/Common/Protocols/PurchaseFlowManaging.swift @@ -1,7 +1,7 @@ import SoraFoundation -protocol PurchaseHandler { - func handlePurchase( +protocol PurchaseFlowManaging { + func startPuchaseFlow( from view: ControllerBackedProtocol?, purchaseActions: [PurchaseAction], wireframe: (PurchasePresentable & AlertPresentable)?, @@ -9,8 +9,8 @@ protocol PurchaseHandler { ) } -extension PurchaseHandler where Self: ModalPickerViewControllerDelegate & PurchaseDelegate { - func handlePurchase( +extension PurchaseFlowManaging where Self: ModalPickerViewControllerDelegate & PurchaseDelegate { + func startPuchaseFlow( from view: ControllerBackedProtocol?, purchaseActions: [PurchaseAction], wireframe: (PurchasePresentable & AlertPresentable)?, @@ -20,7 +20,7 @@ extension PurchaseHandler where Self: ModalPickerViewControllerDelegate & Purcha return } if purchaseActions.count == 1 { - handlePurchase(from: view, purchaseAction: purchaseActions[0], wireframe: wireframe, locale: locale) + startPuchaseFlow(from: view, purchaseAction: purchaseActions[0], wireframe: wireframe, locale: locale) } else { wireframe?.showPurchaseProviders( from: view, @@ -30,7 +30,7 @@ extension PurchaseHandler where Self: ModalPickerViewControllerDelegate & Purcha } } - func handlePurchase( + func startPuchaseFlow( from view: ControllerBackedProtocol?, purchaseAction: PurchaseAction, wireframe: (PurchasePresentable & AlertPresentable)?, diff --git a/novawallet/Common/Protocols/PurchasePresentable.swift b/novawallet/Common/Protocols/PurchasePresentable.swift index 604331de1c..67cc3affbd 100644 --- a/novawallet/Common/Protocols/PurchasePresentable.swift +++ b/novawallet/Common/Protocols/PurchasePresentable.swift @@ -1,3 +1,5 @@ +import Foundation + protocol PurchasePresentable { func showPurchaseTokens( from view: ControllerBackedProtocol?, @@ -10,9 +12,14 @@ protocol PurchasePresentable { actions: [PurchaseAction], delegate: ModalPickerViewControllerDelegate ) + + func presentPurchaseDidComplete( + view: ControllerBackedProtocol?, + locale: Locale + ) } -extension PurchasePresentable where Self: AlertPresentable { +extension PurchasePresentable { func showPurchaseTokens( from view: ControllerBackedProtocol?, action: PurchaseAction, @@ -45,4 +52,16 @@ extension PurchasePresentable where Self: AlertPresentable { } 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 index 926f752908..6a539b6d12 100644 --- a/novawallet/Common/PurchaseProvider/BanxaProvider.swift +++ b/novawallet/Common/PurchaseProvider/BanxaProvider.swift @@ -4,6 +4,7 @@ import SubstrateSdk final class BanxaProvider: PurchaseProviderProtocol { #if F_RELEASE + // TODO: Add production host let host = "" #else let host = "https://novawallet.banxa-sandbox.com" diff --git a/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift b/novawallet/Modules/AssetDetails/AssetDetailsPresenter.swift index 3ecf8b9f13..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: PurchaseHandler { +final class AssetDetailsPresenter: PurchaseFlowManaging { weak var view: AssetDetailsViewProtocol? let wireframe: AssetDetailsWireframeProtocol let viewModelFactory: AssetDetailsViewModelFactoryProtocol @@ -95,7 +95,7 @@ final class AssetDetailsPresenter: PurchaseHandler { } private func showPurchase() { - handlePurchase( + startPuchaseFlow( from: view, purchaseActions: purchaseActions, wireframe: wireframe, @@ -243,7 +243,12 @@ extension AssetDetailsPresenter: Localizable { extension AssetDetailsPresenter: ModalPickerViewControllerDelegate { func modalPickerDidSelectModelAtIndex(_ index: Int, context _: AnyObject?) { - handlePurchase(from: view, purchaseAction: purchaseActions[index], wireframe: wireframe, locale: selectedLocale) + startPuchaseFlow( + from: view, + purchaseAction: purchaseActions[index], + wireframe: wireframe, + locale: selectedLocale + ) } } diff --git a/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift b/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift index 8067e52747..70fdef2e08 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 & PurchaseHandler { +final class BuyAssetOperationPresenter: AssetsSearchPresenter & PurchaseFlowManaging { var buyAssetWireframe: BuyAssetOperationWireframeProtocol? { wireframe as? BuyAssetOperationWireframeProtocol } @@ -57,7 +57,7 @@ final class BuyAssetOperationPresenter: AssetsSearchPresenter & PurchaseHandler guard let self = self else { return } - self.handlePurchase( + self.startPuchaseFlow( from: self.view, purchaseActions: self.purchaseActions, wireframe: self.buyAssetWireframe, @@ -73,7 +73,7 @@ final class BuyAssetOperationPresenter: AssetsSearchPresenter & PurchaseHandler extension BuyAssetOperationPresenter: ModalPickerViewControllerDelegate { func modalPickerDidSelectModelAtIndex(_ index: Int, context _: AnyObject?) { - handlePurchase( + startPuchaseFlow( from: view, purchaseAction: purchaseActions[index], wireframe: buyAssetWireframe, From 0b76b8cc172818ba70c2a30cbaddc2c9b5503519 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Thu, 28 Sep 2023 13:53:25 +0300 Subject: [PATCH 11/37] cleanup --- .../AssetOperation/Buy/BuyAssetOperationPresenter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift b/novawallet/Modules/AssetsSearch/AssetOperation/Buy/BuyAssetOperationPresenter.swift index 70fdef2e08..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 & PurchaseFlowManaging { +final class BuyAssetOperationPresenter: AssetsSearchPresenter, PurchaseFlowManaging { var buyAssetWireframe: BuyAssetOperationWireframeProtocol? { wireframe as? BuyAssetOperationWireframeProtocol } From 9120db163b6cc14b0bf0711a071bd781eb7520d4 Mon Sep 17 00:00:00 2001 From: Gulnaz <666lynx666@mail.ru> Date: Thu, 28 Sep 2023 13:54:54 +0300 Subject: [PATCH 12/37] init (#836) --- .../Relaychain/ElectedAndPrefValidators.swift | 2 +- .../Model/Relaychain/PreparedNomination.swift | 2 +- .../Model/Relaychain/SelectedStakingOption.swift | 2 +- .../Model/NominationPoolModel.swift | 2 +- .../StakingType/StakingTypePresenter.swift | 15 ++++++++++++--- 5 files changed, 16 insertions(+), 7 deletions(-) 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/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() } From 49116aa0b4b622a8acd829bda23c74ee571b01be Mon Sep 17 00:00:00 2001 From: Gulnaz <666lynx666@mail.ru> Date: Thu, 28 Sep 2023 14:01:49 +0300 Subject: [PATCH 13/37] Support buy providers for erc-20 tokens (#842) * init * fix tests --- .../ChainRegistry/RemoteChain/RemoteAssetModel+Evm.swift | 2 +- .../Model/ChainRegistry/RemoteChain/RemoteEvmToken.swift | 2 ++ novawalletTests/Helper/ChainModelGenerator.swift | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) 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/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) ]) } } From 640d7fd26847999ffa3da5f87d341f4d66d2d272 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Fri, 29 Sep 2023 08:48:32 +0300 Subject: [PATCH 14/37] add cross min stake check --- novawallet.xcodeproj/project.pbxproj | 4 ++ .../Protocols/StakeBaseErrorPresentable.swift | 9 +++ .../Protocols/StakingErrorPresentable.swift | 60 ++++++++++++------ .../StakingUnbondSetupPresenter.swift | 33 +++++++--- .../StakingUnbondSetupViewFactory.swift | 2 +- .../Validation/MinStakeCrossedParams.swift | 7 +++ .../NominationPoolDataValidatorFactory.swift | 6 -- .../StakingDataValidatorFactory.swift | 61 ++++++++++++++----- .../StakingUnbondSetupTests.swift | 2 +- 9 files changed, 133 insertions(+), 51 deletions(-) create mode 100644 novawallet/Modules/Staking/Protocols/StakeBaseErrorPresentable.swift create mode 100644 novawallet/Modules/Staking/Validation/MinStakeCrossedParams.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index b6214f1f12..652d0aaf8a 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -664,6 +664,7 @@ 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 */; }; 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 */; }; @@ -4627,6 +4628,7 @@ 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 = ""; }; 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 = ""; }; @@ -15574,6 +15576,7 @@ 0C13D3102A80CA9A0054BB6F /* StakingDataValidatorFactory+Plank.swift */, 77171CA92A98BC420032B387 /* NominationPoolDataValidatorFactory.swift */, 0C12A2482AA35AE300C7FA49 /* RelaychainStakingValidatorFacade.swift */, + 772AD8E52AC5A87200B0C41A /* MinStakeCrossedParams.swift */, ); path = Validation; sourceTree = ""; @@ -21983,6 +21986,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 */, diff --git a/novawallet/Modules/Staking/Protocols/StakeBaseErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/StakeBaseErrorPresentable.swift new file mode 100644 index 0000000000..2c961d5173 --- /dev/null +++ b/novawallet/Modules/Staking/Protocols/StakeBaseErrorPresentable.swift @@ -0,0 +1,9 @@ +// +// StakeBaseErrorPresentable.swift +// novawallet +// +// Created by Gulnaz Almuhametova on 28.09.2023. +// Copyright © 2023 Nova Foundation. All rights reserved. +// + +import Foundation diff --git a/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift index 945493a002..07be4f8b52 100644 --- a/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift +++ b/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift @@ -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?) @@ -76,6 +70,14 @@ protocol StakingErrorPresentable: BaseErrorPresentable { onClose: @escaping () -> Void, locale: Locale? ) + + func presentCrossedMinStake( + from view: ControllerBackedProtocol?, + minStake: String, + remaining: String, + action: @escaping () -> Void, + locale: Locale + ) } extension StakingErrorPresentable where Self: AlertPresentable & ErrorPresentable { @@ -171,19 +173,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) @@ -310,4 +299,37 @@ extension StakingErrorPresentable where Self: AlertPresentable & ErrorPresentabl present(viewModel: viewModel, style: .alert, from: view) } + + 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/StakingUnbondSetup/StakingUnbondSetupPresenter.swift b/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupPresenter.swift index 6306c343da..bd8c3506fc 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,22 @@ extension StakingUnbondSetupPresenter: StakingUnbondSetupPresenterProtocol { func proceed() { let locale = view?.localizationManager?.selectedLocale ?? Locale.current + + var unbondAmount = inputAmount + let unbondAmountInPlank = unbondAmount?.toSubstrateAmount( + precision: chainAsset.assetDisplayInfo.assetPrecision + ) + let minStakeInPlank = minimalBalance?.toSubstrateAmount( + precision: chainAsset.assetDisplayInfo.assetPrecision + ) + + let minStakeValidationParams = MinStakeCrossedParams( + stakedAmountInPlank: unbondAmountInPlank, + minStake: minStakeInPlank + ) { [weak self] in + unbondAmount = self?.bonded + } + DataValidationRunner(validators: [ dataValidatingFactory.canUnbond(amount: inputAmount, bonded: bonded, locale: locale), @@ -126,14 +143,14 @@ extension StakingUnbondSetupPresenter: StakingUnbondSetupPresenterProtocol { locale: locale ), - dataValidatingFactory.stashIsNotKilledAfterUnbonding( - amount: inputAmount, - bonded: bonded, - minimumAmount: minimalBalance, + dataValidatingFactory.minStakeNotCrossed( + for: unbondAmount ?? 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..700e6d9c9d 100644 --- a/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupViewFactory.swift +++ b/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupViewFactory.swift @@ -29,7 +29,7 @@ struct StakingUnbondSetupViewFactory { wireframe: wireframe, balanceViewModelFactory: balanceViewModelFactory, dataValidatingFactory: dataValidatingFactory, - assetInfo: assetInfo, + chainAsset: chainAsset, logger: Logger.shared ) 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..cc64b8c98c 100644 --- a/novawallet/Modules/Staking/Validation/NominationPoolDataValidatorFactory.swift +++ b/novawallet/Modules/Staking/Validation/NominationPoolDataValidatorFactory.swift @@ -9,12 +9,6 @@ struct ExistentialDepositValidationParams { let amountUpdateClosure: (Decimal) -> Void } -struct MinStakeCrossedParams { - let stakedAmountInPlank: BigUInt? - let minStake: BigUInt? - let unstakeAllHandler: () -> Void -} - protocol NominationPoolDataValidatorFactoryProtocol: BaseDataValidatingFactoryProtocol { func nominationPoolHasApy( pool: NominationPools.SelectedPool, diff --git a/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift b/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift index d44a745b38..2952120591 100644 --- a/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift +++ b/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift @@ -41,10 +41,10 @@ protocol StakingDataValidatingFactoryProtocol: BaseDataValidatingFactoryProtocol locale: Locale ) -> DataValidating - func stashIsNotKilledAfterUnbonding( - amount: Decimal?, - bonded: Decimal?, - minimumAmount: Decimal?, + func minStakeNotCrossed( + for inputAmount: Decimal, + params: MinStakeCrossedParams, + chainAsset: ChainAsset, locale: Locale ) -> DataValidating @@ -219,26 +219,55 @@ extension StakingDataValidatingFactory: StakingDataValidatingFactoryProtocol { }) } - func stashIsNotKilledAfterUnbonding( - amount: Decimal?, - bonded: Decimal?, - minimumAmount: Decimal?, + func minStakeNotCrossed( + for inputAmount: Decimal, + params: MinStakeCrossedParams, + chainAsset: ChainAsset, locale: Locale ) -> DataValidating { - WarningConditionViolation(onWarning: { [weak self] delegate in - guard let view = self?.view else { + 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 } - self?.presentable.presentStashKilledAfterUnbond(from: view, action: { - delegate.didCompleteWarningHandling() - }, locale: locale) + 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: { - if let amount = amount, let bonded = bonded, let minimumAmount = minimumAmount { - return bonded - amount >= minimumAmount - } else { + guard + let stakedAmountInPlank = stakedAmountInPlank, + let minStake = minStake, + stakedAmountInPlank >= inputAmountInPlank else { return false } + + let diff = stakedAmountInPlank - inputAmountInPlank + + return diff == 0 || diff >= minStake }) } 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 From 8a431457271de03810643819695dde90a49af268 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Fri, 29 Sep 2023 08:49:21 +0300 Subject: [PATCH 15/37] fix --- novawallet.xcodeproj/project.pbxproj | 4 ++ .../NominationPoolErrorPresentable.swift | 43 +-------------- .../Protocols/StakeBaseErrorPresentable.swift | 53 ++++++++++++++++--- .../Protocols/StakingErrorPresentable.swift | 43 +-------------- .../StakingUnbondSetupPresenter.swift | 7 +-- .../StakingUnbondSetupViewFactory.swift | 5 +- .../StakingDataValidatorFactory.swift | 14 ++--- 7 files changed, 66 insertions(+), 103 deletions(-) diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 652d0aaf8a..52cdfe8544 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -665,6 +665,7 @@ 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 /* StakeBaseErrorPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772AD8E72AC5E30000B0C41A /* StakeBaseErrorPresentable.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 */; }; @@ -4629,6 +4630,7 @@ 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 /* StakeBaseErrorPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeBaseErrorPresentable.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 = ""; }; @@ -15075,6 +15077,7 @@ 84C7DA5225EE2DD900F8C318 /* Protocols */ = { isa = PBXGroup; children = ( + 772AD8E72AC5E30000B0C41A /* StakeBaseErrorPresentable.swift */, 84C7DA5325EE2DF000F8C318 /* StakingErrorPresentable.swift */, 84350AD3284580F50031EF24 /* StakingTotalStakePresentable.swift */, 84350AD52845836C0031EF24 /* IdentityPresentable.swift */, @@ -20312,6 +20315,7 @@ 84EBFCE7285E7A7C0006327E /* XcmMessage.swift in Sources */, 8424DB0B26B8466A008C834F /* ValidatorOperationFactoryProtocol.swift in Sources */, 848FFE6625E6742400652AA5 /* EraValidatorService.swift in Sources */, + 772AD8E82AC5E30000B0C41A /* StakeBaseErrorPresentable.swift in Sources */, 8493D3E927059B6700157009 /* StakingServiceFactory.swift in Sources */, 887C44FB29AC7B8700950F98 /* DelegateSingleVoteHeader.swift in Sources */, 84729758260AA519009B86D0 /* SelectValidatorsConfirmInteractorBase.swift in Sources */, diff --git a/novawallet/Modules/Staking/Protocols/NominationPoolErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/NominationPoolErrorPresentable.swift index dbc152cfe9..27fe55753d 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: StakeBaseErrorPresentable { 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/StakeBaseErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/StakeBaseErrorPresentable.swift index 2c961d5173..be98264317 100644 --- a/novawallet/Modules/Staking/Protocols/StakeBaseErrorPresentable.swift +++ b/novawallet/Modules/Staking/Protocols/StakeBaseErrorPresentable.swift @@ -1,9 +1,46 @@ -// -// StakeBaseErrorPresentable.swift -// novawallet -// -// Created by Gulnaz Almuhametova on 28.09.2023. -// Copyright © 2023 Nova Foundation. All rights reserved. -// - import Foundation + +protocol StakeBaseErrorPresentable: BaseErrorPresentable { + func presentCrossedMinStake( + from view: ControllerBackedProtocol?, + minStake: String, + remaining: String, + action: @escaping () -> Void, + locale: Locale + ) +} + +extension StakeBaseErrorPresentable 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 07be4f8b52..5796b4b77f 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: BaseErrorPresentable, StakeBaseErrorPresentable { func presentAmountTooLow(value: String, from view: ControllerBackedProtocol, locale: Locale?) func presentMissingController( @@ -70,14 +70,6 @@ protocol StakingErrorPresentable: BaseErrorPresentable { onClose: @escaping () -> Void, locale: Locale? ) - - func presentCrossedMinStake( - from view: ControllerBackedProtocol?, - minStake: String, - remaining: String, - action: @escaping () -> Void, - locale: Locale - ) } extension StakingErrorPresentable where Self: AlertPresentable & ErrorPresentable { @@ -299,37 +291,4 @@ extension StakingErrorPresentable where Self: AlertPresentable & ErrorPresentabl present(viewModel: viewModel, style: .alert, from: view) } - - 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/StakingUnbondSetup/StakingUnbondSetupPresenter.swift b/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupPresenter.swift index bd8c3506fc..a2a6b36880 100644 --- a/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupPresenter.swift +++ b/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupPresenter.swift @@ -114,7 +114,8 @@ extension StakingUnbondSetupPresenter: StakingUnbondSetupPresenterProtocol { let locale = view?.localizationManager?.selectedLocale ?? Locale.current var unbondAmount = inputAmount - let unbondAmountInPlank = unbondAmount?.toSubstrateAmount( + + let bondedAmountInPlank = bonded?.toSubstrateAmount( precision: chainAsset.assetDisplayInfo.assetPrecision ) let minStakeInPlank = minimalBalance?.toSubstrateAmount( @@ -122,7 +123,7 @@ extension StakingUnbondSetupPresenter: StakingUnbondSetupPresenterProtocol { ) let minStakeValidationParams = MinStakeCrossedParams( - stakedAmountInPlank: unbondAmountInPlank, + stakedAmountInPlank: bondedAmountInPlank, minStake: minStakeInPlank ) { [weak self] in unbondAmount = self?.bonded @@ -144,7 +145,7 @@ extension StakingUnbondSetupPresenter: StakingUnbondSetupPresenterProtocol { ), dataValidatingFactory.minStakeNotCrossed( - for: unbondAmount ?? 0, + for: inputAmount ?? 0, params: minStakeValidationParams, chainAsset: chainAsset, locale: locale diff --git a/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupViewFactory.swift b/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupViewFactory.swift index 700e6d9c9d..4e84c2e08b 100644 --- a/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupViewFactory.swift +++ b/novawallet/Modules/Staking/StakingUnbondSetup/StakingUnbondSetupViewFactory.swift @@ -22,7 +22,10 @@ struct StakingUnbondSetupViewFactory { priceAssetInfoFactory: priceAssetInfoFactory ) - let dataValidatingFactory = StakingDataValidatingFactory(presentable: wireframe) + let dataValidatingFactory = StakingDataValidatingFactory( + presentable: wireframe, + balanceFactory: balanceViewModelFactory + ) let presenter = StakingUnbondSetupPresenter( interactor: interactor, diff --git a/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift b/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift index 2952120591..d1bf8db884 100644 --- a/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift +++ b/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift @@ -41,13 +41,6 @@ protocol StakingDataValidatingFactoryProtocol: BaseDataValidatingFactoryProtocol locale: Locale ) -> DataValidating - func minStakeNotCrossed( - for inputAmount: Decimal, - params: MinStakeCrossedParams, - chainAsset: ChainAsset, - locale: Locale - ) -> DataValidating - func ledgerNotExist( stakingLedger: StakingLedger?, locale: Locale @@ -77,6 +70,13 @@ protocol StakingDataValidatingFactoryProtocol: BaseDataValidatingFactoryProtocol ) -> DataValidating func allowsNewNominators(flag: Bool, locale: Locale) -> DataValidating + + func minStakeNotCrossed( + for inputAmount: Decimal, + params: MinStakeCrossedParams, + chainAsset: ChainAsset, + locale: Locale + ) -> DataValidating } final class StakingDataValidatingFactory { From 4b2b6deae44a102f0b5319097172e668b0729b53 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Fri, 29 Sep 2023 08:55:08 +0300 Subject: [PATCH 16/37] cleanup --- .../Modules/Staking/Protocols/StakingErrorPresentable.swift | 2 +- .../Staking/Validation/StakingDataValidatorFactory.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift index 5796b4b77f..2ba686ed6b 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, StakeBaseErrorPresentable { +protocol StakingErrorPresentable: StakeBaseErrorPresentable { func presentAmountTooLow(value: String, from view: ControllerBackedProtocol, locale: Locale?) func presentMissingController( diff --git a/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift b/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift index d1bf8db884..fcd6bc11f4 100644 --- a/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift +++ b/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift @@ -70,7 +70,7 @@ protocol StakingDataValidatingFactoryProtocol: BaseDataValidatingFactoryProtocol ) -> DataValidating func allowsNewNominators(flag: Bool, locale: Locale) -> DataValidating - + func minStakeNotCrossed( for inputAmount: Decimal, params: MinStakeCrossedParams, From 5a29ddc72e5fcd1650288b1160f75e2cd2f52a36 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Fri, 29 Sep 2023 08:59:20 +0300 Subject: [PATCH 17/37] renaming --- novawallet.xcodeproj/project.pbxproj | 8 ++++---- .../Protocols/NominationPoolErrorPresentable.swift | 2 +- ...resentable.swift => StakingBaseErrorPresentable.swift} | 4 ++-- .../Staking/Protocols/StakingErrorPresentable.swift | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename novawallet/Modules/Staking/Protocols/{StakeBaseErrorPresentable.swift => StakingBaseErrorPresentable.swift} (89%) diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 52cdfe8544..dd45eb561b 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -665,7 +665,7 @@ 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 /* StakeBaseErrorPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 772AD8E72AC5E30000B0C41A /* StakeBaseErrorPresentable.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 */; }; @@ -4630,7 +4630,7 @@ 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 /* StakeBaseErrorPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeBaseErrorPresentable.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 = ""; }; @@ -15077,7 +15077,7 @@ 84C7DA5225EE2DD900F8C318 /* Protocols */ = { isa = PBXGroup; children = ( - 772AD8E72AC5E30000B0C41A /* StakeBaseErrorPresentable.swift */, + 772AD8E72AC5E30000B0C41A /* StakingBaseErrorPresentable.swift */, 84C7DA5325EE2DF000F8C318 /* StakingErrorPresentable.swift */, 84350AD3284580F50031EF24 /* StakingTotalStakePresentable.swift */, 84350AD52845836C0031EF24 /* IdentityPresentable.swift */, @@ -20315,7 +20315,7 @@ 84EBFCE7285E7A7C0006327E /* XcmMessage.swift in Sources */, 8424DB0B26B8466A008C834F /* ValidatorOperationFactoryProtocol.swift in Sources */, 848FFE6625E6742400652AA5 /* EraValidatorService.swift in Sources */, - 772AD8E82AC5E30000B0C41A /* StakeBaseErrorPresentable.swift in Sources */, + 772AD8E82AC5E30000B0C41A /* StakingBaseErrorPresentable.swift in Sources */, 8493D3E927059B6700157009 /* StakingServiceFactory.swift in Sources */, 887C44FB29AC7B8700950F98 /* DelegateSingleVoteHeader.swift in Sources */, 84729758260AA519009B86D0 /* SelectValidatorsConfirmInteractorBase.swift in Sources */, diff --git a/novawallet/Modules/Staking/Protocols/NominationPoolErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/NominationPoolErrorPresentable.swift index 27fe55753d..d30b59c1e3 100644 --- a/novawallet/Modules/Staking/Protocols/NominationPoolErrorPresentable.swift +++ b/novawallet/Modules/Staking/Protocols/NominationPoolErrorPresentable.swift @@ -1,6 +1,6 @@ import Foundation -protocol NominationPoolErrorPresentable: StakeBaseErrorPresentable { +protocol NominationPoolErrorPresentable: StakingBaseErrorPresentable { func presentNominationPoolHasNoApy( from view: ControllerBackedProtocol, action: @escaping () -> Void, diff --git a/novawallet/Modules/Staking/Protocols/StakeBaseErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/StakingBaseErrorPresentable.swift similarity index 89% rename from novawallet/Modules/Staking/Protocols/StakeBaseErrorPresentable.swift rename to novawallet/Modules/Staking/Protocols/StakingBaseErrorPresentable.swift index be98264317..716fe89aed 100644 --- a/novawallet/Modules/Staking/Protocols/StakeBaseErrorPresentable.swift +++ b/novawallet/Modules/Staking/Protocols/StakingBaseErrorPresentable.swift @@ -1,6 +1,6 @@ import Foundation -protocol StakeBaseErrorPresentable: BaseErrorPresentable { +protocol StakingBaseErrorPresentable: BaseErrorPresentable { func presentCrossedMinStake( from view: ControllerBackedProtocol?, minStake: String, @@ -10,7 +10,7 @@ protocol StakeBaseErrorPresentable: BaseErrorPresentable { ) } -extension StakeBaseErrorPresentable where Self: AlertPresentable & ErrorPresentable { +extension StakingBaseErrorPresentable where Self: AlertPresentable & ErrorPresentable { func presentCrossedMinStake( from view: ControllerBackedProtocol?, minStake: String, diff --git a/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift index 2ba686ed6b..085c7d82fd 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: StakeBaseErrorPresentable { +protocol StakingErrorPresentable: StakingBaseErrorPresentable { func presentAmountTooLow(value: String, from view: ControllerBackedProtocol, locale: Locale?) func presentMissingController( From cf40a9d1840a313ed806163e8ebfc2cb793ccd0b Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Fri, 29 Sep 2023 09:03:32 +0300 Subject: [PATCH 18/37] remove unused strings --- novawallet/en.lproj/Localizable.strings | 2 -- novawallet/ru.lproj/Localizable.strings | 2 -- 2 files changed, 4 deletions(-) diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index ea814a6119..dee2c6c19b 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -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: %@"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index bfe829d819..727d94a671 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" = "Можно забрать: %@"; From 98fda3c8c6b4191ab460b1e52b67ef35fdf02a51 Mon Sep 17 00:00:00 2001 From: Gulnaz <666lynx666@mail.ru> Date: Fri, 29 Sep 2023 09:36:50 +0300 Subject: [PATCH 19/37] Fix text for max nominators reached warning (#838) * init * fix new lines * fix titles --- novawallet.xcodeproj/project.pbxproj | 12 +++--- .../NominationPoolSearchInteractor.swift | 0 .../Search}/NominationPoolSearchManager.swift | 0 ...NominationPoolSearchOperationFactory.swift | 0 .../NominationPoolSearchPresenter.swift | 0 .../NominationPoolSearchProtocols.swift | 0 .../NominationPoolSearchViewController.swift | 0 .../NominationPoolSearchViewFactory.swift | 0 .../NominationPoolSearchViewLayout.swift | 0 .../NominationPoolSearchWireframe.swift | 0 .../Selection}/Model/ButtonViewModel.swift | 0 .../StakingSelectPoolViewModelFactory.swift | 0 .../StakingSelectPoolInteractor.swift | 0 .../StakingSelectPoolPresenter.swift | 0 .../StakingSelectPoolProtocols.swift | 0 .../StakingSelectPoolViewController.swift | 0 .../StakingSelectPoolViewFactory.swift | 0 .../StakingSelectPoolWireframe.swift | 0 .../View/StakingPoolTableViewCell.swift | 0 .../Selection}/View/StakingPoolView.swift | 0 .../StakingSelectPoolListHeaderView.swift | 0 .../View/StakingSelectPoolViewLayout.swift | 0 .../View/StakingSelectPoolViewStyles.swift | 0 .../Protocols/StakingErrorPresentable.swift | 15 ++++++-- .../StartStakingDirectConfirmPresenter.swift | 4 ++ .../StartStakingPoolConfirmPresenter.swift | 4 ++ .../StartStakingConfirmPresenter.swift | 5 +++ .../RelaychainStakingValidatorFacade.swift | 3 ++ .../StakingDataValidatorFactory.swift | 37 +++++++++++++++++-- novawallet/en.lproj/Localizable.strings | 8 ++-- novawallet/ru.lproj/Localizable.strings | 7 ++-- 31 files changed, 75 insertions(+), 20 deletions(-) rename novawallet/Modules/Staking/{NominationPoolSearch => NominationPools/Search}/NominationPoolSearchInteractor.swift (100%) rename novawallet/Modules/Staking/{NominationPoolSearch => NominationPools/Search}/NominationPoolSearchManager.swift (100%) rename novawallet/Modules/Staking/{NominationPoolSearch => NominationPools/Search}/NominationPoolSearchOperationFactory.swift (100%) rename novawallet/Modules/Staking/{NominationPoolSearch => NominationPools/Search}/NominationPoolSearchPresenter.swift (100%) rename novawallet/Modules/Staking/{NominationPoolSearch => NominationPools/Search}/NominationPoolSearchProtocols.swift (100%) rename novawallet/Modules/Staking/{NominationPoolSearch => NominationPools/Search}/NominationPoolSearchViewController.swift (100%) rename novawallet/Modules/Staking/{NominationPoolSearch => NominationPools/Search}/NominationPoolSearchViewFactory.swift (100%) rename novawallet/Modules/Staking/{NominationPoolSearch => NominationPools/Search}/NominationPoolSearchViewLayout.swift (100%) rename novawallet/Modules/Staking/{NominationPoolSearch => NominationPools/Search}/NominationPoolSearchWireframe.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/Model/ButtonViewModel.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/Model/StakingSelectPoolViewModelFactory.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/StakingSelectPoolInteractor.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/StakingSelectPoolPresenter.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/StakingSelectPoolProtocols.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/StakingSelectPoolViewController.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/StakingSelectPoolViewFactory.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/StakingSelectPoolWireframe.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/View/StakingPoolTableViewCell.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/View/StakingPoolView.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/View/StakingSelectPoolListHeaderView.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/View/StakingSelectPoolViewLayout.swift (100%) rename novawallet/Modules/Staking/{StakingSelectPool => NominationPools/Selection}/View/StakingSelectPoolViewStyles.swift (100%) diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index b6214f1f12..06cbae88d2 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -8453,6 +8453,8 @@ F4CFBAC4468FAD5728719A7D /* ClaimRewards */, 0CB261D62A9893BD00287305 /* Unstake */, D1AABAFE54CA7C8321264E64 /* BondMore */, + ADE9FF1DB92333FA89C7F683 /* Selection */, + F3286B3F0CD8E7D358AC93EA /* Search */, ); path = NominationPools; sourceTree = ""; @@ -16025,9 +16027,7 @@ CCD822B8B9ED8FF81C834DD5 /* StartStakingInfo */, 7066B343B912F72345D541F2 /* StakingSetupAmount */, 17432B4B5D8D9DC5C22CA238 /* StakingType */, - ADE9FF1DB92333FA89C7F683 /* StakingSelectPool */, 92CDAD21CEED554306CAF5D8 /* StartStakingConfirm */, - F3286B3F0CD8E7D358AC93EA /* NominationPoolSearch */, ); path = Staking; sourceTree = ""; @@ -17150,7 +17150,7 @@ path = AddToken; sourceTree = ""; }; - ADE9FF1DB92333FA89C7F683 /* StakingSelectPool */ = { + ADE9FF1DB92333FA89C7F683 /* Selection */ = { isa = PBXGroup; children = ( 770F57892A8A48F7005FD7C1 /* Model */, @@ -17162,7 +17162,7 @@ E7F38FDB907F69A26B93B4E6 /* StakingSelectPoolViewController.swift */, 4E553E3D45A7A759C917A4B2 /* StakingSelectPoolViewFactory.swift */, ); - path = StakingSelectPool; + path = Selection; sourceTree = ""; }; ADEC075C60AA6D00785D2BDF /* AssetList */ = { @@ -18153,7 +18153,7 @@ path = Purchase; sourceTree = ""; }; - F3286B3F0CD8E7D358AC93EA /* NominationPoolSearch */ = { + F3286B3F0CD8E7D358AC93EA /* Search */ = { isa = PBXGroup; children = ( A31B8F292DA050D1D19B9F5F /* NominationPoolSearchProtocols.swift */, @@ -18166,7 +18166,7 @@ 77895C9E2A8F5D40006870FB /* NominationPoolSearchManager.swift */, 77895CA02A8F7360006870FB /* NominationPoolSearchOperationFactory.swift */, ); - path = NominationPoolSearch; + path = Search; sourceTree = ""; }; F3E50DB617DFDE30FE6FA185 /* ParaStkUnstake */ = { 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 100% rename from novawallet/Modules/Staking/NominationPoolSearch/NominationPoolSearchViewController.swift rename to novawallet/Modules/Staking/NominationPools/Search/NominationPoolSearchViewController.swift 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/Protocols/StakingErrorPresentable.swift b/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift index 945493a002..86a2030fac 100644 --- a/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift +++ b/novawallet/Modules/Staking/Protocols/StakingErrorPresentable.swift @@ -53,7 +53,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, @@ -228,12 +232,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/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/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/StakingDataValidatorFactory.swift b/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift index d44a745b38..2ab3c062af 100644 --- a/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift +++ b/novawallet/Modules/Staking/Validation/StakingDataValidatorFactory.swift @@ -76,7 +76,11 @@ 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 { @@ -321,7 +325,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 +431,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/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index ea814a6119..f30586f201 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -414,8 +414,8 @@ "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.is.not.available.title" = "%@ is currently\nunavailable"; +"staking.max.nominators.reached.message" = "The maximum number of nominators\nhas 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)"; @@ -1371,8 +1371,8 @@ "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.message" = "You cannot join selected pool since it reached maximum number of members"; +"staking.pool.is.full.title" = "Pool is full"; +"staking.pool.is.full.message" = "You cannot join pool since it reached maximum number of members"; "common.back" = "Back"; "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"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index bfe829d819..67d553a373 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -415,7 +415,8 @@ "staking.set.validators.title" = "Выберите валидаторов для начала стейкинга"; "staking.set.validators.message" = "Валидаторы не выбраны"; "staking.max.nominators.reached.title" = "Невозможно начать стейкинг"; -"staking.max.nominators.reached.message" = "Достигнуто максимальное количество номинаторов"; +"staking.is.not.available.title" = "%@ сейчас\nнедоступен"; +"staking.max.nominators.reached.message" = "Достигнуто максимальное количество номинаторов. Пожалуйста, попробуйте позже"; "staking.custom.blocked.warning" = "Этот валидатор не принимает номинации в данный момент. Пожалуйста, попробуйте снова в следующую эру."; "staking.alert.start.next.era.message" = "Пожалуйста, дождитесь начала следующей эры."; "staking.custom.proceed.button.disabled.title" = "Выбрать валидаторов (макс. %li)"; @@ -1371,8 +1372,8 @@ "common.time.period.after" = "через"; "common.time.period.every" = "раз в"; "staking.search.pool.placeholder" = "Поиск по имени или ID"; -"staking.pool.is.full.title" = "Выбранный пул заполнен"; -"staking.pool.is.full.message" = "Вы не можете присоединиться к выбранному пулу так как в нём не осталось места"; +"staking.pool.is.full.title" = "Пул заполнен"; +"staking.pool.is.full.message" = "Вы не можете присоединиться к пулу так как в нём не осталось мест"; "common.back" = "Назад"; "asset.operation.send.empty.state.message" = "У вас нет токенов для отправки.\nКупите или получите токены\nна свой аккаунт."; "governance.referendums.status.deciding" = "На рассмотрении"; From 820cb04b7764ee491490845dd9e605af9bae4ebc Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Fri, 29 Sep 2023 11:26:20 +0300 Subject: [PATCH 20/37] init --- .../AssetOperation/AssetOperationViewFactory.swift | 6 +++--- .../AssetSearch/AssetsSearchViewController.swift | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/novawallet/Modules/AssetsSearch/AssetOperation/AssetOperationViewFactory.swift b/novawallet/Modules/AssetsSearch/AssetOperation/AssetOperationViewFactory.swift index 67f007dfe0..71302402c0 100644 --- a/novawallet/Modules/AssetsSearch/AssetOperation/AssetOperationViewFactory.swift +++ b/novawallet/Modules/AssetsSearch/AssetOperation/AssetOperationViewFactory.swift @@ -33,7 +33,7 @@ enum AssetOperationViewFactory { let view = AssetsSearchViewController( presenter: presenter, - keyboardAppearanceStrategy: ModalNavigationKeyboardStrategy(), + keyboardAppearanceStrategy: nil, createViewClosure: { AssetsOperationViewLayout() }, localizableTitle: title, localizationManager: LocalizationManager.shared @@ -75,7 +75,7 @@ enum AssetOperationViewFactory { let view = AssetsSearchViewController( presenter: presenter, - keyboardAppearanceStrategy: ModalNavigationKeyboardStrategy(), + keyboardAppearanceStrategy: nil, createViewClosure: { AssetsOperationViewLayout() }, localizableTitle: title, localizationManager: LocalizationManager.shared @@ -116,7 +116,7 @@ enum AssetOperationViewFactory { let view = SendAssetOperationViewController( presenter: presenter, - keyboardAppearanceStrategy: ModalNavigationKeyboardStrategy(), + keyboardAppearanceStrategy: nil, createViewClosure: { AssetsOperationViewLayout() }, localizableTitle: title, localizationManager: LocalizationManager.shared diff --git a/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift b/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift index ec16dfb8a1..1287e97f7c 100644 --- a/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift +++ b/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift @@ -14,11 +14,11 @@ class AssetsSearchViewController: UIViewController, ViewHolder { private let createViewClosure: () -> BaseAssetsSearchViewLayout private let localizableTitle: LocalizableResource? - let keyboardAppearanceStrategy: KeyboardAppearanceStrategyProtocol + let keyboardAppearanceStrategy: KeyboardAppearanceStrategyProtocol? init( presenter: AssetsSearchPresenterProtocol, - keyboardAppearanceStrategy: KeyboardAppearanceStrategyProtocol, + keyboardAppearanceStrategy: KeyboardAppearanceStrategyProtocol?, createViewClosure: @escaping () -> BaseAssetsSearchViewLayout, localizableTitle: LocalizableResource? = nil, localizationManager: LocalizationManagerProtocol @@ -53,13 +53,13 @@ class AssetsSearchViewController: UIViewController, ViewHolder { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - keyboardAppearanceStrategy.onViewWillAppear(for: rootView.searchBar.textField) + keyboardAppearanceStrategy?.onViewWillAppear(for: rootView.searchBar.textField) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - keyboardAppearanceStrategy.onViewDidAppear(for: rootView.searchBar.textField) + keyboardAppearanceStrategy?.onViewDidAppear(for: rootView.searchBar.textField) } override func viewDidDisappear(_ animated: Bool) { From e227023409dc7162b9993e06f29be1ccc56583b6 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Fri, 29 Sep 2023 18:03:50 +0300 Subject: [PATCH 21/37] show keyboard on view will appear --- .../Common/Helpers/KeyboardAppearanceState.swift | 11 +++++++++++ .../AssetOperation/AssetOperationViewFactory.swift | 6 +++--- .../AssetSearch/AssetsSearchViewController.swift | 9 +++++---- 3 files changed, 19 insertions(+), 7 deletions(-) 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/Modules/AssetsSearch/AssetOperation/AssetOperationViewFactory.swift b/novawallet/Modules/AssetsSearch/AssetOperation/AssetOperationViewFactory.swift index 71302402c0..67f007dfe0 100644 --- a/novawallet/Modules/AssetsSearch/AssetOperation/AssetOperationViewFactory.swift +++ b/novawallet/Modules/AssetsSearch/AssetOperation/AssetOperationViewFactory.swift @@ -33,7 +33,7 @@ enum AssetOperationViewFactory { let view = AssetsSearchViewController( presenter: presenter, - keyboardAppearanceStrategy: nil, + keyboardAppearanceStrategy: ModalNavigationKeyboardStrategy(), createViewClosure: { AssetsOperationViewLayout() }, localizableTitle: title, localizationManager: LocalizationManager.shared @@ -75,7 +75,7 @@ enum AssetOperationViewFactory { let view = AssetsSearchViewController( presenter: presenter, - keyboardAppearanceStrategy: nil, + keyboardAppearanceStrategy: ModalNavigationKeyboardStrategy(), createViewClosure: { AssetsOperationViewLayout() }, localizableTitle: title, localizationManager: LocalizationManager.shared @@ -116,7 +116,7 @@ enum AssetOperationViewFactory { let view = SendAssetOperationViewController( presenter: presenter, - keyboardAppearanceStrategy: nil, + keyboardAppearanceStrategy: ModalNavigationKeyboardStrategy(), createViewClosure: { AssetsOperationViewLayout() }, localizableTitle: title, localizationManager: LocalizationManager.shared diff --git a/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift b/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift index 1287e97f7c..36a26fe054 100644 --- a/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift +++ b/novawallet/Modules/AssetsSearch/AssetSearch/AssetsSearchViewController.swift @@ -14,11 +14,11 @@ class AssetsSearchViewController: UIViewController, ViewHolder { private let createViewClosure: () -> BaseAssetsSearchViewLayout private let localizableTitle: LocalizableResource? - let keyboardAppearanceStrategy: KeyboardAppearanceStrategyProtocol? + let keyboardAppearanceStrategy: KeyboardAppearanceStrategyProtocol init( presenter: AssetsSearchPresenterProtocol, - keyboardAppearanceStrategy: KeyboardAppearanceStrategyProtocol?, + keyboardAppearanceStrategy: KeyboardAppearanceStrategyProtocol, createViewClosure: @escaping () -> BaseAssetsSearchViewLayout, localizableTitle: LocalizableResource? = nil, localizationManager: LocalizationManagerProtocol @@ -53,13 +53,13 @@ class AssetsSearchViewController: UIViewController, ViewHolder { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - keyboardAppearanceStrategy?.onViewWillAppear(for: rootView.searchBar.textField) + keyboardAppearanceStrategy.onViewWillAppear(for: rootView.searchBar.textField) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - keyboardAppearanceStrategy?.onViewDidAppear(for: rootView.searchBar.textField) + keyboardAppearanceStrategy.onViewDidAppear(for: rootView.searchBar.textField) } override func viewDidDisappear(_ animated: Bool) { @@ -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) From df9605462197aa83e3bb167a074b58e63d019235 Mon Sep 17 00:00:00 2001 From: Holyberry <666lynx666@mail.ru> Date: Fri, 29 Sep 2023 18:35:25 +0300 Subject: [PATCH 22/37] add base staking validation factory --- novawallet.xcodeproj/project.pbxproj | 6 +- .../NominationPoolDataValidatorFactory.swift | 72 ++--------------- .../StakingBaseDataValidatingFactory.swift | 77 +++++++++++++++++++ .../StakingDataValidatorFactory.swift | 76 ++---------------- 4 files changed, 94 insertions(+), 137 deletions(-) create mode 100644 novawallet/Modules/Staking/Validation/StakingBaseDataValidatingFactory.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index dd45eb561b..de94df1fa2 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -649,6 +649,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 */; }; @@ -4613,6 +4614,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 = ""; }; @@ -15571,13 +15573,14 @@ 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 */, ); @@ -22354,6 +22357,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 */, diff --git a/novawallet/Modules/Staking/Validation/NominationPoolDataValidatorFactory.swift b/novawallet/Modules/Staking/Validation/NominationPoolDataValidatorFactory.swift index cc64b8c98c..6c4d7da9db 100644 --- a/novawallet/Modules/Staking/Validation/NominationPoolDataValidatorFactory.swift +++ b/novawallet/Modules/Staking/Validation/NominationPoolDataValidatorFactory.swift @@ -9,7 +9,7 @@ struct ExistentialDepositValidationParams { let amountUpdateClosure: (Decimal) -> Void } -protocol NominationPoolDataValidatorFactoryProtocol: BaseDataValidatingFactoryProtocol { +protocol NominationPoolDataValidatorFactoryProtocol: StakingBaseDataValidatingFactoryProtocol { func nominationPoolHasApy( pool: NominationPools.SelectedPool, locale: Locale @@ -40,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?, @@ -78,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) } } @@ -269,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/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 fcd6bc11f4..e4efd2c245 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 @@ -70,27 +70,17 @@ protocol StakingDataValidatingFactoryProtocol: BaseDataValidatingFactoryProtocol ) -> DataValidating func allowsNewNominators(flag: Bool, locale: Locale) -> DataValidating - - func minStakeNotCrossed( - for inputAmount: Decimal, - params: MinStakeCrossedParams, - chainAsset: ChainAsset, - 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,58 +209,6 @@ extension StakingDataValidatingFactory: StakingDataValidatingFactoryProtocol { }) } - 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 hasRedeemable(stakingLedger: StakingLedger?, in era: UInt32?, locale: Locale) -> DataValidating { ErrorConditionViolation(onError: { [weak self] in guard let view = self?.view else { From 4fe2e5827ba7e4078532bbfb5983a16350157af4 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sun, 1 Oct 2023 10:13:29 +0500 Subject: [PATCH 23/37] fix custom signed extension config --- Podfile | 4 ++-- Podfile.lock | 12 ++++++------ .../RuntimeSnapshotOperationFactory.swift | 12 ++++++------ .../Substrate/Extension/ExtrinsicExtension.swift | 10 +++++++--- novawalletTests/Helper/RuntimeHelper.swift | 4 ++-- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/Podfile b/Podfile index dcec44c231..7e8c7e2a96 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', :commit => '30fb77603d32bd425994d02faa410f093266e168' 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', :commit => '30fb77603d32bd425994d02faa410f093266e168' 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..fafae96e40 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`, commit `30fb77603d32bd425994d02faa410f093266e168`) - SVGKit (from `https://github.com/SVGKit/SVGKit.git`, tag `3.0.0`) - SwiftAlgorithms (~> 1.0.0) - SwiftFormat/CLI (~> 0.47.13) @@ -219,8 +219,8 @@ EXTERNAL SOURCES: :git: https://github.com/ERussel/Starscream.git :tag: 4.0.8 SubstrateSdk: + :commit: 30fb77603d32bd425994d02faa410f093266e168 :git: https://github.com/nova-wallet/substrate-sdk-ios.git - :tag: 1.13.0 SVGKit: :git: https://github.com/SVGKit/SVGKit.git :tag: 3.0.0 @@ -250,8 +250,8 @@ CHECKOUT OPTIONS: :git: https://github.com/ERussel/Starscream.git :tag: 4.0.8 SubstrateSdk: + :commit: 30fb77603d32bd425994d02faa410f093266e168 :git: https://github.com/nova-wallet/substrate-sdk-ios.git - :tag: 1.13.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: 739c641f15330cc90495b60a56dcfc22d18cf94e COCOAPODS: 1.12.1 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/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() ) From 3eb6b911eb5805b64d91f515f21e3b2c433d8366 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sun, 1 Oct 2023 16:48:41 +0500 Subject: [PATCH 24/37] fix tests --- .../StakingRebondSetup/StakingRebondSetupTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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() + } } } From ca0b4baf090f19281753abe378712e35468a665d Mon Sep 17 00:00:00 2001 From: ERussel Date: Mon, 2 Oct 2023 18:43:07 +0500 Subject: [PATCH 25/37] fix pod lock --- Podfile | 4 ++-- Podfile.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Podfile b/Podfile index 7e8c7e2a96..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', :commit => '30fb77603d32bd425994d02faa410f093266e168' + 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', :commit => '30fb77603d32bd425994d02faa410f093266e168' + 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 fafae96e40..9500b91c99 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -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`, commit `30fb77603d32bd425994d02faa410f093266e168`) + - 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) @@ -219,8 +219,8 @@ EXTERNAL SOURCES: :git: https://github.com/ERussel/Starscream.git :tag: 4.0.8 SubstrateSdk: - :commit: 30fb77603d32bd425994d02faa410f093266e168 :git: https://github.com/nova-wallet/substrate-sdk-ios.git + :tag: 1.14.0 SVGKit: :git: https://github.com/SVGKit/SVGKit.git :tag: 3.0.0 @@ -250,8 +250,8 @@ CHECKOUT OPTIONS: :git: https://github.com/ERussel/Starscream.git :tag: 4.0.8 SubstrateSdk: - :commit: 30fb77603d32bd425994d02faa410f093266e168 :git: https://github.com/nova-wallet/substrate-sdk-ios.git + :tag: 1.14.0 SVGKit: :git: https://github.com/SVGKit/SVGKit.git :tag: 3.0.0 @@ -303,6 +303,6 @@ SPEC CHECKSUMS: ZMarkupParser: a92d31ba40695b790f1da5fec98c3d4505341aff ZNSTextAttachment: 4a9b4e8ee1ed087fc893ae6657dfb678f1a00340 -PODFILE CHECKSUM: 739c641f15330cc90495b60a56dcfc22d18cf94e +PODFILE CHECKSUM: f37e3724d47617fb7ce7ed5e0a583491617b5899 COCOAPODS: 1.12.1 From 5e5d9b4f682c88e0d3b0b07bfacd49815937ed8c Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 4 Oct 2023 16:06:46 +0500 Subject: [PATCH 26/37] add production domain for banxa --- novawallet/Common/PurchaseProvider/BanxaProvider.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/novawallet/Common/PurchaseProvider/BanxaProvider.swift b/novawallet/Common/PurchaseProvider/BanxaProvider.swift index 6a539b6d12..4a84078d45 100644 --- a/novawallet/Common/PurchaseProvider/BanxaProvider.swift +++ b/novawallet/Common/PurchaseProvider/BanxaProvider.swift @@ -4,8 +4,7 @@ import SubstrateSdk final class BanxaProvider: PurchaseProviderProtocol { #if F_RELEASE - // TODO: Add production host - let host = "" + let host = "https://novawallet.banxa.com" #else let host = "https://novawallet.banxa-sandbox.com" #endif From c70519ff3250f6c13bb9dee0c61a9462bb5f5242 Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 4 Oct 2023 17:16:57 +0500 Subject: [PATCH 27/37] sync lokalize --- novawallet/en.lproj/Localizable.strings | 12 ++++++------ novawallet/ru.lproj/Localizable.strings | 13 ++++++------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index e0899b3b2b..4ad914f33f 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"; @@ -412,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.is.not.available.title" = "%@ is currently\nunavailable"; -"staking.max.nominators.reached.message" = "The maximum number of nominators\nhas been reached. Try again later"; +"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)"; @@ -666,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"; @@ -1332,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?"; @@ -1370,9 +1369,10 @@ "common.time.period.every" = "every"; "staking.search.pool.placeholder" = "Search by name or pool ID"; "staking.pool.is.full.title" = "Pool is full"; -"staking.pool.is.full.message" = "You cannot join pool since it reached maximum number of members"; +"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" = "You are leaving Nova Wallet"; -"common.alert.external.link.disclaimer.message" = "You will be redirected to %@"; +"common.alert.external.link.disclaimer.message" = "You will be redirected to %@"; \ No newline at end of file diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index a7b0c2fee5..d3aaa28c28 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -412,8 +412,6 @@ "staking.custom.header.validators.title" = "Валидаторов: %li из %li"; "staking.set.validators.title" = "Выберите валидаторов для начала стейкинга"; "staking.set.validators.message" = "Валидаторы не выбраны"; -"staking.max.nominators.reached.title" = "Невозможно начать стейкинг"; -"staking.is.not.available.title" = "%@ сейчас\nнедоступен"; "staking.max.nominators.reached.message" = "Достигнуто максимальное количество номинаторов. Пожалуйста, попробуйте позже"; "staking.custom.blocked.warning" = "Этот валидатор не принимает номинации в данный момент. Пожалуйста, попробуйте снова в следующую эру."; "staking.alert.start.next.era.message" = "Пожалуйста, дождитесь начала следующей эры."; @@ -666,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" = "Ваш перевод завершится ошибкой, так как у получателя недостаточно %@ для приема переводов в других токенах."; @@ -1333,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Вы уверены, что хотите продолжить с выбранным пулом?"; @@ -1371,9 +1369,10 @@ "common.time.period.every" = "раз в"; "staking.search.pool.placeholder" = "Поиск по имени или ID"; "staking.pool.is.full.title" = "Пул заполнен"; -"staking.pool.is.full.message" = "Вы не можете присоединиться к пулу так как в нём не осталось мест"; +"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" = "Вы покидаете Nova Wallet"; -"common.alert.external.link.disclaimer.message" = "Вы будете перенаправлены на сайт %@"; +"common.alert.external.link.disclaimer.message" = "Вы будете перенаправлены на сайт %@"; \ No newline at end of file From 51c6fbc83d4bbeeb41865c97e46dcb5b14bdd1e8 Mon Sep 17 00:00:00 2001 From: Stepan Lavrentev Date: Wed, 4 Oct 2023 15:37:00 +0300 Subject: [PATCH 28/37] remove KDW from coingeko --- novawallet/Resources/currencies.json | 8 -------- 1 file changed, 8 deletions(-) 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", From f03b3c2eaf63f857322a9034ba214a08957c1e9a Mon Sep 17 00:00:00 2001 From: Gulnaz <666lynx666@mail.ru> Date: Thu, 5 Oct 2023 12:15:14 +0300 Subject: [PATCH 29/37] init (#855) --- .../Common/PurchaseProvider/PurchaseAggregator+Default.swift | 4 ++-- novawallet/en.lproj/Localizable.strings | 4 ++-- novawallet/ru.lproj/Localizable.strings | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift b/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift index 5175595dd0..36415d2190 100644 --- a/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift +++ b/novawallet/Common/PurchaseProvider/PurchaseAggregator+Default.swift @@ -5,9 +5,9 @@ extension PurchaseAggregator { let config: ApplicationConfigProtocol = ApplicationConfig.shared let purchaseProviders: [PurchaseProviderProtocol] = [ - MercuryoProvider(), TransakProvider(), - BanxaProvider() + BanxaProvider(), + MercuryoProvider() ] return PurchaseAggregator(providers: purchaseProviders) .with(appName: config.purchaseAppName) diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index 4ad914f33f..bd5f6556fb 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1374,5 +1374,5 @@ "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" = "You are leaving Nova Wallet"; -"common.alert.external.link.disclaimer.message" = "You will be redirected to %@"; \ No newline at end of file +"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 %@"; diff --git a/novawallet/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index d3aaa28c28..e10f79e4f6 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1374,5 +1374,5 @@ "wallet.list.empty.action.title" = "Купить токены"; "asset.operation.send.empty.state.message" = "У вас нет токенов для отправки.\nКупите или получите токены\nна свой аккаунт."; "governance.referendums.status.deciding" = "Решение"; -"common.alert.external.link.disclaimer.title" = "Вы покидаете Nova Wallet"; -"common.alert.external.link.disclaimer.message" = "Вы будете перенаправлены на сайт %@"; \ No newline at end of file +"common.alert.external.link.disclaimer.title" = "Продолжить в браузере?"; +"common.alert.external.link.disclaimer.message" = "Для продолжения покупки вы будете перенаправлены из приложения Nova Wallet на сайт %@"; From 7ed2fdb2288b04d4c3c5b6e60cbbaf9cd7262df3 Mon Sep 17 00:00:00 2001 From: ERussel Date: Fri, 20 Oct 2023 22:11:03 +0200 Subject: [PATCH 30/37] complete banner logic --- novawallet.xcodeproj/project.pbxproj | 20 ++++ .../iconCloseWithBg.imageset/Contents.json | 12 ++ .../iconCloseWithBg.pdf | Bin 0 -> 2400 bytes .../Contents.json | 12 ++ .../imagePolkadotStakingBg.pdf | Bin 0 -> 4085 bytes .../Contents.json | 12 ++ .../imagePolkadotStakingPromo.pdf | Bin 0 -> 216095 bytes .../Common/Extension/SettingsExtension.swift | 11 ++ .../UIKit/RoundedButton+Styles.swift | 10 ++ .../Extension/UIKit/Style/UILabel+Style.swift | 10 ++ .../PromotionBannerView.swift | 107 ++++++++++++++++++ .../ViewModel/PromotionViewModelFactory.swift | 12 ++ .../AssetList/AssetListInteractor.swift | 10 ++ .../AssetList/AssetListPresenter.swift | 34 ++++++ .../AssetList/AssetListProtocols.swift | 8 ++ .../AssetList/AssetListViewController.swift | 53 ++++++++- .../AssetList/AssetListWireframe.swift | 8 ++ .../AssetList/View/AssetListBannerCell.swift | 29 +++++ .../AssetList/View/AssetListFlowLayout.swift | 33 +++++- novawallet/en.lproj/Localizable.strings | 2 + novawallet/ru.lproj/Localizable.strings | 2 + 21 files changed, 379 insertions(+), 6 deletions(-) create mode 100644 novawallet/Assets.xcassets/iconCloseWithBg.imageset/Contents.json create mode 100644 novawallet/Assets.xcassets/iconCloseWithBg.imageset/iconCloseWithBg.pdf create mode 100644 novawallet/Assets.xcassets/imagePolkadotStakingBg.imageset/Contents.json create mode 100644 novawallet/Assets.xcassets/imagePolkadotStakingBg.imageset/imagePolkadotStakingBg.pdf create mode 100644 novawallet/Assets.xcassets/imagePolkadotStakingPromo.imageset/Contents.json create mode 100644 novawallet/Assets.xcassets/imagePolkadotStakingPromo.imageset/imagePolkadotStakingPromo.pdf create mode 100644 novawallet/Common/View/PromotionBannerView/PromotionBannerView.swift create mode 100644 novawallet/Common/ViewModel/PromotionViewModelFactory.swift create mode 100644 novawallet/Modules/AssetList/View/AssetListBannerCell.swift diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index 0837e78b08..b4469f594d 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 */; }; @@ -4159,6 +4162,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 = ""; }; @@ -8420,6 +8426,14 @@ path = LocalFilter; sourceTree = ""; }; + 0C9951CD2AE2BAA700B65615 /* PromotionBannerView */ = { + isa = PBXGroup; + children = ( + 0C9951CE2AE2BAE500B65615 /* PromotionBannerView.swift */, + ); + path = PromotionBannerView; + sourceTree = ""; + }; 0C9C642B2A8CE2D4004DC078 /* SystemAccounts */ = { isa = PBXGroup; children = ( @@ -13258,6 +13272,7 @@ 8490149D24AA7F9A008F705E /* View */ = { isa = PBXGroup; children = ( + 0C9951CD2AE2BAA700B65615 /* PromotionBannerView */, 0C79C8932A7BDED700B171E3 /* TitleHorizontalMultivalueView */, 84F96FA32A136E0400EB70E2 /* GladingView */, 84B24FA12A2F1C6C00F9BF59 /* CollectionView */, @@ -13537,6 +13552,7 @@ 0C13380F2AB832B30036BCD6 /* QRImageViewModel.swift */, 0C1338112AB8330D0036BCD6 /* AnimatedImageViewModel.swift */, 0C1338132AB834750036BCD6 /* QRImageViewModelFactory.swift */, + 0C9951D22AE2DB0200B65615 /* PromotionViewModelFactory.swift */, ); path = ViewModel; sourceTree = ""; @@ -15715,6 +15731,7 @@ 843E9B2727C83985009C143A /* AssetListNftsCell.swift */, 88DC3E26292CABCB00DBCE4D /* AssetListStyles.swift */, 0C9ECB592A4A9AB400BDCA73 /* AssetListAccountCell.swift */, + 0C9951C92AE2ABA400B65615 /* AssetListBannerCell.swift */, ); path = View; sourceTree = ""; @@ -19650,6 +19667,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 */, @@ -20636,6 +20654,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 */, @@ -21239,6 +21258,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 */, 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 0000000000000000000000000000000000000000..78df52c7bbb929788c3d3fdb4a9000b1375605f0 GIT binary patch literal 2400 zcma)8OK;pZ5We$Q@M0i2M9cRB2n;l~6BKPxcimgigDNY|7VA~JQVO@fKHpH>k(_mb z@?g(?9M0pLAx9Ut*YDrxCJLdYlEd$R2r1vbm7ANV>BHUBPW}ALlz&|-Rg;p!l9!jz zJX@O^y=WTuUz(SfbiO~_lS5&idH%6KJWQXZ1-K=xaXR+xqu5;cfA6|}`{A9ux?BJ1 zM)8**zYn9(4S7U#>Lk2j>3om3x<>)oY8&Adf;ZWb;9XWmj@^N_XT#c@q;jT$sR<@J z9i?w7oWu^aS*?h5}N;A!q@~XA0!JXAs6^upc4AVY3GcUuCcVTd=#m1PB|Z8 z$NEg=-vjf>DTuN0%0%lViV|G%p1kE~qA!S7*osY7O6MdyoI^xL)22o*84UrJVo^6a zCPkJUg3v7Fa?a$WyeaC%Xffb58Gy;8DNVTXDa}aE0q06E*e_Vna8m-#salGb&QV$+ zLWfe14(Dq67QO0puudUo-B=Y(&Wj#l*bRwd^~vWAcU-IT(c!(95{gv$A$JtDwOC!Nc5R*;C#OdAvuNxDTJhw4{H@dVu`9+ zs)Qt*tNysoov@7o+g30Aut)4wuMlFD2R9toOi1CO##ISf#Z?HQN)r#P1wx|2`+CZ- z60(Y`IJU}`bCs>CPFj`GYLfg_-Wef(wK|M(1=$kUdF7A$p%N02GxrVa5r*qP-Nn{s z!ULyY`!od#xg<}0T8mfGCy*MW6u3OGRB~z`YJ)%}M*(IY=C(usFvFifNT_}e@y zH^2V#XD>Im?L%LIpZfb<`)+uWA5WC6X4mqbFVe}B}#B)hxkaSiC?u- zyB{8&AALIZpM;W8%-4nf%HX~A5^!A_tk9B}#PiY?Dbiv{Cx`y|2BO#C8D#L>e+y~7 z9Gs5rZr>jTeFJ@e4LV+p!(;zizS`Zto=MqkhhdVGa3OeexBIt8^yN#XJx(PZqhk^m J7dJn@{|aw)?9~7O literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..79327b3e1121c826a25e240d3fd0b7b298e4f7a3 GIT binary patch literal 4085 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�# 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 n@S%7f;15RHISe6dVhe*Q6X=s>Oe)GQo 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/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..7984d3f9bb 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 = false 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,12 @@ extension AssetListPresenter: AssetListInteractorOutputProtocol { func didCompleteRefreshing() { view?.didCompleteRefreshing() } + + func didReceivePromotionBanner(shouldShowPolkadotStaking: Bool) { + shouldShowPolkadotPromotion = shouldShowPolkadotStaking + + providePolkadotStakingPromotion() + } } extension AssetListPresenter: Localizable { @@ -509,6 +542,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..96bb17e47a 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() } } @@ -200,6 +204,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 +371,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 +400,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: @@ -444,4 +470,29 @@ extension AssetListViewController: AssetListViewProtocol { func didCompleteRefreshing() { rootView.collectionView.refreshControl?.endRefreshing() } + + func didReceivePromotion(viewModel: PromotionBannerView.ViewModel) { + promotionBannerViewModel = viewModel + + rootView.collectionView.reloadData() + } + + 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]) + } + } +} + +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/View/AssetListBannerCell.swift b/novawallet/Modules/AssetList/View/AssetListBannerCell.swift new file mode 100644 index 0000000000..ea60e739b7 --- /dev/null +++ b/novawallet/Modules/AssetList/View/AssetListBannerCell.swift @@ -0,0 +1,29 @@ +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) + } +} diff --git a/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift b/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift index 42edeb2a71..4bb6e3a122 100644 --- a/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift +++ b/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift @@ -6,6 +6,7 @@ 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 @@ -20,6 +21,7 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { case summary case nfts case settings + case promotion case assetGroup init(section: Int) { @@ -29,6 +31,8 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { case 1: self = .nfts case 2: + self = .promotion + case 3: self = .settings default: self = .assetGroup @@ -41,10 +45,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,7 +70,7 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { switch self { case .summary: return 10.0 - case .settings, .assetGroup, .nfts: + case .settings, .assetGroup, .nfts, .promotion: return 0 } } @@ -75,6 +81,8 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { return UIEdgeInsets(top: 0, left: 0, bottom: 12, right: 0) case .nfts: return UIEdgeInsets(top: 0, left: 0, bottom: 8, right: 0) + case .promotion: + return .zero case .settings: return .zero case .assetGroup: @@ -87,6 +95,7 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { case account case totalBalance case yourNfts + case banner case settings case asset(sectionIndex: Int, itemIndex: Int) case emptyState @@ -98,6 +107,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 +123,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) } @@ -186,6 +199,14 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { groupY += AssetListMeasurement.nftsHeight } + groupY += SectionType.promotion.insets.top + SectionType.promotion.insets.bottom + + let hasPromotion = collectionView.numberOfItems(inSection: SectionType.promotion.index) > 0 + + if hasPromotion { + groupY += AssetListMeasurement.bannerHeight + } + groupY += SectionType.settings.insets.top + AssetListMeasurement.settingsHeight + SectionType.settings.insets.bottom @@ -242,6 +263,8 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { return totalBalanceHeight case .yourNfts: return AssetListMeasurement.nftsHeight + case .banner: + return AssetListMeasurement.bannerHeight case .settings: return AssetListMeasurement.settingsHeight case .emptyState: diff --git a/novawallet/en.lproj/Localizable.strings b/novawallet/en.lproj/Localizable.strings index bd5f6556fb..ea8d826ba8 100644 --- a/novawallet/en.lproj/Localizable.strings +++ b/novawallet/en.lproj/Localizable.strings @@ -1376,3 +1376,5 @@ "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 e10f79e4f6..195f8704a2 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1376,3 +1376,5 @@ "governance.referendums.status.deciding" = "Решение"; "common.alert.external.link.disclaimer.title" = "Продолжить в браузере?"; "common.alert.external.link.disclaimer.message" = "Для продолжения покупки вы будете перенаправлены из приложения Nova Wallet на сайт %@"; +"polkadot.staking.promotion.title" = "Максимизируйте награды от DOT 🚀"; +"polkadot.staking.promotion.message" = "Получили свои DOT из краудлоунов? Начните стейкать DOT уже сегодня, чтобы получить максимальные вознаграждения!"; From 53eaa25d6998bbb0f0d06cbd61a67fd46dc6d646 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sat, 21 Oct 2023 00:19:41 +0200 Subject: [PATCH 31/37] fix assets list layout --- .../PromotionBannerView.swift | 59 ++++++++-- .../AssetList/AssetListPresenter.swift | 3 +- .../AssetList/AssetListViewController.swift | 11 +- .../AssetList/View/AssetListBannerCell.swift | 8 ++ .../AssetList/View/AssetListFlowLayout.swift | 103 +++++++++++++----- novawallet/ru.lproj/Localizable.strings | 2 +- 6 files changed, 148 insertions(+), 38 deletions(-) diff --git a/novawallet/Common/View/PromotionBannerView/PromotionBannerView.swift b/novawallet/Common/View/PromotionBannerView/PromotionBannerView.swift index 0091db77f8..147bb284ed 100644 --- a/novawallet/Common/View/PromotionBannerView/PromotionBannerView.swift +++ b/novawallet/Common/View/PromotionBannerView/PromotionBannerView.swift @@ -9,11 +9,12 @@ final class PromotionBannerView: UIView { let backgroundView = UIImageView() let titleLabel: UILabel = .create { label in - label.apply(style: .semiboldSubhedlinePrimary) + label.apply(style: Constants.titleStyle) + label.numberOfLines = 0 } let detailsLabel: UILabel = .create { label in - label.apply(style: .footnotePrimary) + label.apply(style: Constants.detailsStyle) label.numberOfLines = 0 } @@ -60,23 +61,21 @@ final class PromotionBannerView: UIView { addSubview(iconImageView) iconImageView.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(24.0) + make.trailing.equalToSuperview().inset(Constants.contentInset.right) make.centerY.equalToSuperview() } let descriptionView = UIView.vStack( - alignment: .fill, - spacing: 8.0, + spacing: Constants.descriptionVerticalSpacing, [titleLabel, detailsLabel] ) addSubview(descriptionView) descriptionView.snp.makeConstraints { make in - make.leading.equalToSuperview().inset(16) - make.top.equalToSuperview().inset(12) - make.bottom.equalToSuperview().inset(16) - make.trailing.lessThanOrEqualTo(iconImageView.snp.leading).offset(-8) + 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) @@ -105,3 +104,45 @@ extension PromotionBannerView { 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/Modules/AssetList/AssetListPresenter.swift b/novawallet/Modules/AssetList/AssetListPresenter.swift index 7984d3f9bb..8e7e55ec6c 100644 --- a/novawallet/Modules/AssetList/AssetListPresenter.swift +++ b/novawallet/Modules/AssetList/AssetListPresenter.swift @@ -20,7 +20,7 @@ final class AssetListPresenter { private var walletType: MetaAccountModelType? private var name: String? private var hidesZeroBalances: Bool? - private var shouldShowPolkadotPromotion: Bool = false + private var shouldShowPolkadotPromotion: Bool = true private(set) var walletConnectSessionsCount: Int = 0 @@ -532,7 +532,6 @@ extension AssetListPresenter: AssetListInteractorOutputProtocol { func didReceivePromotionBanner(shouldShowPolkadotStaking: Bool) { shouldShowPolkadotPromotion = shouldShowPolkadotStaking - providePolkadotStakingPromotion() } } diff --git a/novawallet/Modules/AssetList/AssetListViewController.swift b/novawallet/Modules/AssetList/AssetListViewController.swift index 96bb17e47a..f40a0277a1 100644 --- a/novawallet/Modules/AssetList/AssetListViewController.swift +++ b/novawallet/Modules/AssetList/AssetListViewController.swift @@ -189,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) } } @@ -465,6 +466,9 @@ extension AssetListViewController: AssetListViewProtocol { nftViewModel = viewModel rootView.collectionView.reloadData() + + let isNftActive = viewModel != nil + rootView.collectionViewLayout.setNftsActive(isNftActive) } func didCompleteRefreshing() { @@ -475,6 +479,9 @@ extension AssetListViewController: AssetListViewProtocol { promotionBannerViewModel = viewModel rootView.collectionView.reloadData() + + let height = AssetListBannerCell.estimateHeight(for: viewModel) + rootView.collectionViewLayout.activatePromotionWithHeight(height) } func didClosePromotion() { @@ -488,6 +495,8 @@ extension AssetListViewController: AssetListViewProtocol { let indexPath = AssetListFlowLayout.CellType.banner.indexPath self?.rootView.collectionView.deleteItems(at: [indexPath]) } + + rootView.collectionViewLayout.deactivatePromotion() } } diff --git a/novawallet/Modules/AssetList/View/AssetListBannerCell.swift b/novawallet/Modules/AssetList/View/AssetListBannerCell.swift index ea60e739b7..793144440d 100644 --- a/novawallet/Modules/AssetList/View/AssetListBannerCell.swift +++ b/novawallet/Modules/AssetList/View/AssetListBannerCell.swift @@ -27,3 +27,11 @@ final class AssetListBannerCell: UICollectionViewCell { 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 4bb6e3a122..e03989b160 100644 --- a/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift +++ b/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift @@ -11,12 +11,21 @@ enum AssetListMeasurement { 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 @@ -74,21 +83,6 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { 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 .promotion: - return .zero - case .settings: - return .zero - case .assetGroup: - return UIEdgeInsets(top: 2.0, left: 0, bottom: 16.0, right: 0) - } - } } enum CellType { @@ -171,6 +165,7 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { updateItemsBackgroundAttributesIfNeeded() } + // swiftlint:disable:next function_body_length private func updateItemsBackgroundAttributesIfNeeded() { guard let collectionView = collectionView, @@ -189,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 @@ -199,16 +194,16 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { groupY += AssetListMeasurement.nftsHeight } - groupY += SectionType.promotion.insets.top + SectionType.promotion.insets.bottom + groupY += promotionInsets.top + promotionInsets.bottom let hasPromotion = collectionView.numberOfItems(inSection: SectionType.promotion.index) > 0 if hasPromotion { - groupY += AssetListMeasurement.bannerHeight + groupY += promotionHeight } - groupY += SectionType.settings.insets.top + AssetListMeasurement.settingsHeight + - SectionType.settings.insets.bottom + groupY += AssetListMeasurement.settingsInsets.top + AssetListMeasurement.settingsHeight + + AssetListMeasurement.settingsInsets.bottom let initAttributes = [UICollectionViewLayoutAttributes]() let (attributes, _) = (0 ..< groupsCount).reduce((initAttributes, groupY)) { result, groupIndex in @@ -220,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( @@ -236,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] @@ -255,6 +250,49 @@ 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 updatePromotionBannerInsets(_ insets: UIEdgeInsets) { + guard promotionInsets != insets else { + return + } + + promotionInsets = insets + invalidateLayout() + } + func cellHeight(for type: CellType) -> CGFloat { switch type { case .account: @@ -264,7 +302,7 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { case .yourNfts: return AssetListMeasurement.nftsHeight case .banner: - return AssetListMeasurement.bannerHeight + return promotionHeight case .settings: return AssetListMeasurement.settingsHeight case .emptyState: @@ -273,4 +311,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/ru.lproj/Localizable.strings b/novawallet/ru.lproj/Localizable.strings index 195f8704a2..ee84d1cd86 100644 --- a/novawallet/ru.lproj/Localizable.strings +++ b/novawallet/ru.lproj/Localizable.strings @@ -1376,5 +1376,5 @@ "governance.referendums.status.deciding" = "Решение"; "common.alert.external.link.disclaimer.title" = "Продолжить в браузере?"; "common.alert.external.link.disclaimer.message" = "Для продолжения покупки вы будете перенаправлены из приложения Nova Wallet на сайт %@"; -"polkadot.staking.promotion.title" = "Максимизируйте награды от DOT 🚀"; +"polkadot.staking.promotion.title" = "Максимизируйте\nнаграды от DOT 🚀"; "polkadot.staking.promotion.message" = "Получили свои DOT из краудлоунов? Начните стейкать DOT уже сегодня, чтобы получить максимальные вознаграждения!"; From 0c4608cb9bcb3f1af522d6ea23452b1e9bfcaab5 Mon Sep 17 00:00:00 2001 From: ERussel Date: Sat, 21 Oct 2023 01:18:08 +0200 Subject: [PATCH 32/37] remove unused code --- .../Modules/AssetList/View/AssetListFlowLayout.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift b/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift index e03989b160..0584ae6260 100644 --- a/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift +++ b/novawallet/Modules/AssetList/View/AssetListFlowLayout.swift @@ -284,15 +284,6 @@ final class AssetListFlowLayout: UICollectionViewFlowLayout { invalidateLayout() } - func updatePromotionBannerInsets(_ insets: UIEdgeInsets) { - guard promotionInsets != insets else { - return - } - - promotionInsets = insets - invalidateLayout() - } - func cellHeight(for type: CellType) -> CGFloat { switch type { case .account: From 8184d122b450242190197c04c52635718a170456 Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 25 Oct 2023 13:55:59 +0200 Subject: [PATCH 33/37] remove unused files --- Podfile.lock | 2 +- novawallet.xcodeproj/project.pbxproj | 4 -- .../AcalaContributionSource.swift | 38 +++---------------- .../AcalaLiquidContributionResponse.swift | 5 --- 4 files changed, 7 insertions(+), 42 deletions(-) delete mode 100644 novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaLiquidContributionResponse.swift diff --git a/Podfile.lock b/Podfile.lock index 9500b91c99..7581bee044 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -305,4 +305,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: f37e3724d47617fb7ce7ed5e0a583491617b5899 -COCOAPODS: 1.12.1 +COCOAPODS: 1.13.0 diff --git a/novawallet.xcodeproj/project.pbxproj b/novawallet.xcodeproj/project.pbxproj index b4469f594d..6e119aa64f 100644 --- a/novawallet.xcodeproj/project.pbxproj +++ b/novawallet.xcodeproj/project.pbxproj @@ -3699,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 */; }; @@ -7708,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 = ""; }; @@ -17945,7 +17943,6 @@ D9046DB927451ED700C29F2E /* ParallelContributionSource.swift */, D9046DBB27453D5C00C29F2E /* ParallelContributionResponse.swift */, 8459A9C727469E4B000D6278 /* AcalaContributionSource.swift */, - D9D1636727451C2400681C1F /* AcalaLiquidContributionResponse.swift */, ); path = ExternalContibution; sourceTree = ""; @@ -19113,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 */, diff --git a/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaContributionSource.swift b/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaContributionSource.swift index 838d2ee047..81591c4ce9 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,20 @@ 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) - + func getContributions( + accountId _: AccountId, + chain _: ChainModel + ) -> CompoundOperationWrapper<[ExternalContribution]> { 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)] + return [ExternalContribution(source: sourceName, amount: 0, paraId: paraId)] } - let dependencies = [networkOperation] + paraIdWrapper.allOperations - mergeOperation.addDependency(networkOperation) mergeOperation.addDependency(paraIdWrapper.targetOperation) - return CompoundOperationWrapper(targetOperation: mergeOperation, dependencies: dependencies) + return CompoundOperationWrapper(targetOperation: mergeOperation, dependencies: paraIdWrapper.allOperations) } } 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 -} From eee7879185a9b330965847f9cb7780a18710d052 Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 25 Oct 2023 14:16:53 +0200 Subject: [PATCH 34/37] fix crashes --- .../Nft/NftList/NftListInteractor.swift | 16 +++++----- .../StakingDashboardInteractor.swift | 6 +++- ...CrowdloanYourContributionsInteractor.swift | 30 +++++++++---------- 3 files changed, 28 insertions(+), 24 deletions(-) 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/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) } } } From 2605ede022a99d5417f258eb79151f3edaaab3ca Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 25 Oct 2023 14:59:07 +0200 Subject: [PATCH 35/37] fix balance update on transfer all --- novawallet/Modules/AssetList/Base/AssetListBaseBuilder.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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) } From bc0fb82628ba4766eec9dc8e74ac86f5cddbcc9f Mon Sep 17 00:00:00 2001 From: ERussel Date: Wed, 25 Oct 2023 16:22:02 +0200 Subject: [PATCH 36/37] remove parallel api --- .../AcalaContributionSource.swift | 12 +------ .../ParallelContributionSource.swift | 33 +++---------------- 2 files changed, 6 insertions(+), 39 deletions(-) diff --git a/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaContributionSource.swift b/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaContributionSource.swift index 81591c4ce9..b312985978 100644 --- a/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaContributionSource.swift +++ b/novawallet/Modules/Vote/Crowdloan/Operation/ExternalContibution/AcalaContributionSource.swift @@ -17,16 +17,6 @@ final class AcalaContributionSource: ExternalContributionSourceProtocol { accountId _: AccountId, chain _: ChainModel ) -> CompoundOperationWrapper<[ExternalContribution]> { - let paraIdWrapper = paraIdOperationFactory.createParaIdOperation(for: acalaChainId) - - let mergeOperation = ClosureOperation<[ExternalContribution]> { [sourceName] in - let paraId = try paraIdWrapper.targetOperation.extractNoCancellableResultData() - - return [ExternalContribution(source: sourceName, amount: 0, paraId: paraId)] - } - - mergeOperation.addDependency(paraIdWrapper.targetOperation) - - return CompoundOperationWrapper(targetOperation: mergeOperation, dependencies: paraIdWrapper.allOperations) + CompoundOperationWrapper.createWithResult([]) } } 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([]) } } From 7c7231162f06c06201d73fa296cbbb8f4ce3f822 Mon Sep 17 00:00:00 2001 From: ERussel Date: Thu, 26 Oct 2023 09:17:25 +0200 Subject: [PATCH 37/37] bump chain.json version --- novawallet/Common/Configs/ApplicationConfigs.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 }