From 622c8e58c713066a1dd49df861bd54cf179546ff Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Thu, 14 Nov 2024 14:30:18 +0000 Subject: [PATCH 01/22] - separate token display to show both available and staked balance - update library to fix staked balance issue --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../Modules/Account/AccountViewModel.swift | 32 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 26b8b965..394e3e06 100644 --- a/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -70,7 +70,7 @@ "location" : "https://github.com/kukai-wallet/kukai-core-swift", "state" : { "branch" : "develop", - "revision" : "054b34731d0c0a51210307ca4058267ed362a8ed" + "revision" : "bb7aff9817c5eff469032c2d75f9b8a1a920b789" } }, { diff --git a/Kukai Mobile/Modules/Account/AccountViewModel.swift b/Kukai Mobile/Modules/Account/AccountViewModel.swift index aea8eb31..a852e6bf 100644 --- a/Kukai Mobile/Modules/Account/AccountViewModel.swift +++ b/Kukai Mobile/Modules/Account/AccountViewModel.swift @@ -19,6 +19,12 @@ struct BackupCellData: Hashable { let id = UUID() } +struct StakedXTZData: Hashable { + let id = UUID() + let tez: XTZAmount + let isUnstakePending: Bool +} + struct UpdateWarningCellData: Hashable { let id = UUID() } @@ -128,6 +134,17 @@ class AccountViewModel: ViewModel, UITableViewDiffableDataSourceHandler { return cell + } else if let obj = item.base as? StakedXTZData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenBalanceCell", for: indexPath) as? TokenBalanceCell { + cell.symbolLabel.text = "Staked XTZ" + cell.balanceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(obj.tez.toNormalisedDecimal() ?? 0, decimalPlaces: obj.tez.decimalPlaces) + cell.favCorner.isHidden = false + // cell.setPriceChange(value: 100) // Will be re-added when we have the actual values + + let totalXtzValue = obj.tez * DependencyManager.shared.coinGeckoService.selectedCurrencyRatePerXTZ + cell.valuelabel.text = DependencyManager.shared.coinGeckoService.format(decimal: totalXtzValue, numberStyle: .currency, maximumFractionDigits: 2) + + return cell + } else if let token = item.base as? Token, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenBalanceCell", for: indexPath) as? TokenBalanceCell { var symbol = token.symbol if symbol == "" { @@ -250,9 +267,10 @@ class AccountViewModel: ViewModel, UITableViewDiffableDataSourceHandler { private func isEmptyAccount() -> Bool { let xtzBalance = DependencyManager.shared.balanceService.account.xtzBalance - let tokenCount = DependencyManager.shared.balanceService.account.tokens.count + let currentAccount = DependencyManager.shared.balanceService.account + let currentAccountTokensCount = (currentAccount.tokens.count + currentAccount.nfts.count) - return (xtzBalance == .zero() && tokenCount == 0) + return (xtzBalance == .zero() && currentAccountTokensCount == 0) } private func handleRefreshForRegularUser(startingData: [AnyHashableSendable], metadata: WalletMetadata?, parentMetadata: WalletMetadata?, selectedAddress: String) -> [AnyHashableSendable] { @@ -319,7 +337,13 @@ class AccountViewModel: ViewModel, UITableViewDiffableDataSourceHandler { data.append(.init(TotalEstimatedValue(tez: totalXTZ, value: totalCurrencyString))) } - data.append(.init(DependencyManager.shared.balanceService.account.xtzBalance)) + data.append(.init(DependencyManager.shared.balanceService.account.availableBalance)) + + let stakedXtz = DependencyManager.shared.balanceService.account.xtzStakedBalance + if stakedXtz > XTZAmount.zero() { + data.append(.init(StakedXTZData(tez: stakedXtz, isUnstakePending: false))) + } + data.append(contentsOf: tokensToDisplay.map({.init($0)})) return data @@ -356,7 +380,7 @@ class AccountViewModel: ViewModel, UITableViewDiffableDataSourceHandler { func token(atIndexPath: IndexPath) -> Token? { let obj = dataSource?.itemIdentifier(for: atIndexPath)?.base - if obj is XTZAmount { + if obj is XTZAmount || obj is StakedXTZData { let account = DependencyManager.shared.balanceService.account return Token.xtz(withAmount: account.xtzBalance, stakedAmount: account.xtzStakedBalance, unstakedAmount: account.xtzUnstakedBalance) From 38e29d2a87c3ab87585e8cd423b005ccda4d1b9f Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Thu, 14 Nov 2024 16:07:41 +0000 Subject: [PATCH 02/22] WIP: - remove old token details cells no longer needed - add new cell for balance/available balance - add new cell for baker and no-baker flow --- Kukai Mobile.xcodeproj/project.pbxproj | 32 +-- .../Account/Cells/TokenDetailsBakerCell.swift | 85 +++++++ .../Account/Cells/TokenDetailsBakerCell.xib | 214 ++++++++++++++++++ .../TokenDetailsBalanceAndBakerCell.swift | 40 ---- ...okenDetailsBalanceAndBakerCell_nobaker.xib | 120 ---------- ...enDetailsBalanceAndBakerCell_nostaking.xib | 93 -------- .../Cells/TokenDetailsBalanceCell.swift | 30 +++ ..._baker.xib => TokenDetailsBalanceCell.xib} | 68 +++--- .../Account/TokenDetailsViewModel.swift | 17 +- 9 files changed, 384 insertions(+), 315 deletions(-) create mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift create mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib delete mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell.swift delete mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nobaker.xib delete mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nostaking.xib create mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift rename Kukai Mobile/Modules/Account/Cells/{TokenDetailsBalanceAndBakerCell_baker.xib => TokenDetailsBalanceCell.xib} (70%) diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index 428f915a..1ae9125d 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -63,9 +63,7 @@ C0172A082A98EC6400163179 /* OnrampViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0172A072A98EC6400163179 /* OnrampViewController.swift */; }; C0172A0A2A98EE4D00163179 /* TitleSubtitleImageContainerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0172A092A98EE4D00163179 /* TitleSubtitleImageContainerCell.swift */; }; C01A633F297842C100278689 /* TokenDetailsChartCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A633E297842C100278689 /* TokenDetailsChartCell.xib */; }; - C01A6343297845D000278689 /* TokenDetailsBalanceAndBakerCell_baker.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A6342297845D000278689 /* TokenDetailsBalanceAndBakerCell_baker.xib */; }; - C01A63452978470000278689 /* TokenDetailsBalanceAndBakerCell_nobaker.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A6344297846FF00278689 /* TokenDetailsBalanceAndBakerCell_nobaker.xib */; }; - C01A63472978471000278689 /* TokenDetailsBalanceAndBakerCell_nostaking.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A63462978471000278689 /* TokenDetailsBalanceAndBakerCell_nostaking.xib */; }; + C01A6343297845D000278689 /* TokenDetailsBalanceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A6342297845D000278689 /* TokenDetailsBalanceCell.xib */; }; C01A6349297847AA00278689 /* TokenDetailsSendCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A6348297847AA00278689 /* TokenDetailsSendCell.xib */; }; C01A634B297847FB00278689 /* TokenDetailsStakingRewardsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A634A297847FB00278689 /* TokenDetailsStakingRewardsCell.xib */; }; C01A634D2978480700278689 /* TokenDetailsActivityHeaderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C01A634C2978480700278689 /* TokenDetailsActivityHeaderCell.xib */; }; @@ -146,11 +144,13 @@ C049E73E28819E2800887B64 /* DiscoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C049E73D28819E2800887B64 /* DiscoverViewController.swift */; }; C049E74028819E3800887B64 /* DiscoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C049E73F28819E3800887B64 /* DiscoverViewModel.swift */; }; C04B20A52930E1AF00C008CD /* ActivityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04B20A42930E1AF00C008CD /* ActivityService.swift */; }; + C04D98562CE64938009491BD /* TokenDetailsBakerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04D98542CE64938009491BD /* TokenDetailsBakerCell.swift */; }; + C04D98572CE64938009491BD /* TokenDetailsBakerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C04D98552CE64938009491BD /* TokenDetailsBakerCell.xib */; }; C04E0EB22ADE7EE7001BF56F /* UpdateWarningCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E0EB12ADE7EE7001BF56F /* UpdateWarningCell.swift */; }; C04E0EB42ADE9216001BF56F /* RequiredUpdateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E0EB32ADE9216001BF56F /* RequiredUpdateViewController.swift */; }; C04E0EB62ADE92CA001BF56F /* AppUpdateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E0EB52ADE92CA001BF56F /* AppUpdateService.swift */; }; C04E2860293F940E00DC4171 /* TokenDetailsChartCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E285F293F940E00DC4171 /* TokenDetailsChartCell.swift */; }; - C04E2864293F94C500DC4171 /* TokenDetailsBalanceAndBakerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2863293F94C500DC4171 /* TokenDetailsBalanceAndBakerCell.swift */; }; + C04E2864293F94C500DC4171 /* TokenDetailsBalanceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2863293F94C500DC4171 /* TokenDetailsBalanceCell.swift */; }; C04E2866293F94D700DC4171 /* TokenDetailsSendCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2865293F94D700DC4171 /* TokenDetailsSendCell.swift */; }; C04E2868293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2867293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift */; }; C04E286A293F950900DC4171 /* TokenDetailsActivityHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2869293F950900DC4171 /* TokenDetailsActivityHeaderCell.swift */; }; @@ -489,9 +489,7 @@ C0172A072A98EC6400163179 /* OnrampViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnrampViewController.swift; sourceTree = ""; }; C0172A092A98EE4D00163179 /* TitleSubtitleImageContainerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleSubtitleImageContainerCell.swift; sourceTree = ""; }; C01A633E297842C100278689 /* TokenDetailsChartCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsChartCell.xib; sourceTree = ""; }; - C01A6342297845D000278689 /* TokenDetailsBalanceAndBakerCell_baker.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBalanceAndBakerCell_baker.xib; sourceTree = ""; }; - C01A6344297846FF00278689 /* TokenDetailsBalanceAndBakerCell_nobaker.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBalanceAndBakerCell_nobaker.xib; sourceTree = ""; }; - C01A63462978471000278689 /* TokenDetailsBalanceAndBakerCell_nostaking.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBalanceAndBakerCell_nostaking.xib; sourceTree = ""; }; + C01A6342297845D000278689 /* TokenDetailsBalanceCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBalanceCell.xib; sourceTree = ""; }; C01A6348297847AA00278689 /* TokenDetailsSendCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsSendCell.xib; sourceTree = ""; }; C01A634A297847FB00278689 /* TokenDetailsStakingRewardsCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsStakingRewardsCell.xib; sourceTree = ""; }; C01A634C2978480700278689 /* TokenDetailsActivityHeaderCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsActivityHeaderCell.xib; sourceTree = ""; }; @@ -570,11 +568,13 @@ C049E73D28819E2800887B64 /* DiscoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverViewController.swift; sourceTree = ""; }; C049E73F28819E3800887B64 /* DiscoverViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverViewModel.swift; sourceTree = ""; }; C04B20A42930E1AF00C008CD /* ActivityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityService.swift; sourceTree = ""; }; + C04D98542CE64938009491BD /* TokenDetailsBakerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsBakerCell.swift; sourceTree = ""; }; + C04D98552CE64938009491BD /* TokenDetailsBakerCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBakerCell.xib; sourceTree = ""; }; C04E0EB12ADE7EE7001BF56F /* UpdateWarningCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateWarningCell.swift; sourceTree = ""; }; C04E0EB32ADE9216001BF56F /* RequiredUpdateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequiredUpdateViewController.swift; sourceTree = ""; }; C04E0EB52ADE92CA001BF56F /* AppUpdateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateService.swift; sourceTree = ""; }; C04E285F293F940E00DC4171 /* TokenDetailsChartCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsChartCell.swift; sourceTree = ""; }; - C04E2863293F94C500DC4171 /* TokenDetailsBalanceAndBakerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsBalanceAndBakerCell.swift; sourceTree = ""; }; + C04E2863293F94C500DC4171 /* TokenDetailsBalanceCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsBalanceCell.swift; sourceTree = ""; }; C04E2865293F94D700DC4171 /* TokenDetailsSendCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsSendCell.swift; sourceTree = ""; }; C04E2867293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsStakingRewardsCell.swift; sourceTree = ""; }; C04E2869293F950900DC4171 /* TokenDetailsActivityHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsActivityHeaderCell.swift; sourceTree = ""; }; @@ -1643,12 +1643,12 @@ C090A6722B0BA1C000F50C76 /* TokenDetailsHeaderCell.swift */, C04E285F293F940E00DC4171 /* TokenDetailsChartCell.swift */, C01A633E297842C100278689 /* TokenDetailsChartCell.xib */, - C04E2863293F94C500DC4171 /* TokenDetailsBalanceAndBakerCell.swift */, - C01A6342297845D000278689 /* TokenDetailsBalanceAndBakerCell_baker.xib */, - C01A6344297846FF00278689 /* TokenDetailsBalanceAndBakerCell_nobaker.xib */, - C01A63462978471000278689 /* TokenDetailsBalanceAndBakerCell_nostaking.xib */, + C04E2863293F94C500DC4171 /* TokenDetailsBalanceCell.swift */, + C01A6342297845D000278689 /* TokenDetailsBalanceCell.xib */, C04E2865293F94D700DC4171 /* TokenDetailsSendCell.swift */, C01A6348297847AA00278689 /* TokenDetailsSendCell.xib */, + C04D98542CE64938009491BD /* TokenDetailsBakerCell.swift */, + C04D98552CE64938009491BD /* TokenDetailsBakerCell.xib */, C04E2867293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift */, C01A634A297847FB00278689 /* TokenDetailsStakingRewardsCell.xib */, C04E2869293F950900DC4171 /* TokenDetailsActivityHeaderCell.swift */, @@ -1802,12 +1802,12 @@ C0BACA512A0BA82E00FADE47 /* CollectiblesCollectionSinglePageCell.xib in Resources */, C054BCA22A680058006BDFBB /* RecoveryPhrase.storyboard in Resources */, C01372B329B206FF0083E297 /* MenuHeaderCell.xib in Resources */, + C04D98572CE64938009491BD /* TokenDetailsBakerCell.xib in Resources */, C0B372882B0CC13500159266 /* CollectibleDetailQuantityCell.xib in Resources */, C08C973D2908165D00249959 /* CollectibleDetailAttributeItemCell.xib in Resources */, C0EA19DF29096E5900E6B40D /* CollectibleDetailAttributeHeaderCell.xib in Resources */, C03708B72A604186002170BF /* MessageCollectionViewCell.xib in Resources */, C01A634F2978481100278689 /* ActivityItemCell.xib in Resources */, - C01A63472978471000278689 /* TokenDetailsBalanceAndBakerCell_nostaking.xib in Resources */, C036462B2A30D4EA00F3F5C8 /* CollectibleDetailPricesCell.xib in Resources */, C0FB90452B8375D70032C8CE /* EmptyCollectionCell.xib in Resources */, C0FBC885292BCEEF00B29921 /* Figtree-Regular.ttf in Resources */, @@ -1821,7 +1821,6 @@ C0EA19C129096D9400E6B40D /* CollectibleDetailImageCell.xib in Resources */, C008B9BB2965D2CA00B17D96 /* CollectibleDetailAVCell.xib in Resources */, C05B0A302A03F9E3005AA803 /* CollectiblesCollectionHeaderSmallCell.xib in Resources */, - C01A63452978470000278689 /* TokenDetailsBalanceAndBakerCell_nobaker.xib in Resources */, C05B0A382A03FC6D005AA803 /* CollectiblesCollectionItemLargeWithTextCell.xib in Resources */, C01A6355297848B200278689 /* TokenDetailsActivityHeaderCell_footer.xib in Resources */, C01372AF29B206E00083E297 /* MenuChoiceCell.xib in Resources */, @@ -1848,7 +1847,7 @@ C06BC53E2A5EE41B00A0D979 /* SearchResultCell.xib in Resources */, C0DAF38D29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.xib in Resources */, C0411B4F29DEC2A500778E1E /* TextFieldSuggestionAccessoryViewCell.xib in Resources */, - C01A6343297845D000278689 /* TokenDetailsBalanceAndBakerCell_baker.xib in Resources */, + C01A6343297845D000278689 /* TokenDetailsBalanceCell.xib in Resources */, C03BF88E2A279836003BD343 /* ActivityItemBatchCell.xib in Resources */, C0678DDB27205FE300DEF1CB /* InfoPlist.strings in Resources */, C0F91C6A28BFB3200081E8E8 /* Stake.storyboard in Resources */, @@ -1988,7 +1987,7 @@ C0248CEE2AA21B5900B1F63C /* String+extensions_shared.swift in Sources */, C003CECD27FDED5D00F64B4C /* CKRecord+extensions.swift in Sources */, C06AEDFD2C00997E005CFDAA /* AddAccountViewController.swift in Sources */, - C04E2864293F94C500DC4171 /* TokenDetailsBalanceAndBakerCell.swift in Sources */, + C04E2864293F94C500DC4171 /* TokenDetailsBalanceCell.swift in Sources */, C044E1312AAF4CB40085652F /* SideMenuSettingsViewModel.swift in Sources */, C08FE23A2AD4343500327BF9 /* BackupViewController.swift in Sources */, C001D5A82A027EA10089EC7A /* CollectiblesFavouritesViewController.swift in Sources */, @@ -2118,6 +2117,7 @@ C01E10EA2BF6185C004A8244 /* MigrationService.swift in Sources */, C0D6C04329DDB93800D890ED /* ImportWalletViewController.swift in Sources */, C03DA8842BFF3B2400B6F46C /* SentryBreadcrumb+extensions.swift in Sources */, + C04D98562CE64938009491BD /* TokenDetailsBakerCell.swift in Sources */, C0F5A8DE29ED85580061DBBD /* EditWalletViewController.swift in Sources */, C0FE432E2AD6D44400312940 /* CustomAVPlayerViewController.swift in Sources */, C02F3CB62BFF77A900FA6383 /* AddWalletViewModel.swift in Sources */, diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift new file mode 100644 index 00000000..030641dc --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift @@ -0,0 +1,85 @@ +// +// TokenDetailsBakerCell.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 14/11/2024. +// + +import UIKit +import KukaiCoreSwift + +protocol TokenDetailsBakerDelegate: AnyObject { + func changeTapped() + func learnTapped() +} + +class TokenDetailsBakerCell: UITableViewCell { + + @IBOutlet weak var bakerIcon: UIImageView! + @IBOutlet weak var bakerLabel: UILabel! + @IBOutlet weak var bakerLabelRightConstraint: NSLayoutConstraint! + @IBOutlet weak var bakerApyLabel: UILabel! + @IBOutlet weak var regularlyVotesTitle: UILabel! + @IBOutlet weak var regularlyVotesIcon: UIImageView! + @IBOutlet weak var freeSpaceTitleLabel: UILabel! + @IBOutlet weak var freeSpaceValueLabel: UILabel! + + @IBOutlet weak var bakerButton: CustomisableButton! + @IBOutlet weak var learnButton: CustomisableButton! + + public weak var delegate: TokenDetailsBakerDelegate? = nil + + func setup(data: TokenDetailsBakerData) { + + bakerButton.customButtonType = .secondary + + if let bakerName = data.bakerName { + + // If we have baker data + MediaProxyService.load(url: data.bakerIcon, to: bakerIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) + + bakerButton.setTitle("Change Baker", for: .normal) + bakerLabelRightConstraint.isActive = true + regularlyVotesTitle.isHidden = false + regularlyVotesIcon.isHidden = false + freeSpaceTitleLabel.isHidden = false + freeSpaceValueLabel.isHidden = false + + bakerLabel.text = bakerName + bakerApyLabel.text = "Est APY: \(data.bakerApy.rounded(scale: 2, roundingMode: .bankers))%" + regularlyVotesIcon.image = UIImage.init(named: "Check")?.withTintColor( data.regularlyVotes ? .colorNamed("BGGood4") : .colorNamed("TxtAlert4")) + + freeSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.format(decimal: data.freeSpace, numberStyle: .currency, maximumFractionDigits: 0) + if data.freeSpace > 0 && data.enoughSpaceForBalance { + freeSpaceTitleLabel.textColor = .colorNamed("Txt10") + freeSpaceValueLabel.textColor = .colorNamed("Txt8") + } else { + freeSpaceTitleLabel.textColor = .colorNamed("TxtAlert4") + freeSpaceValueLabel.textColor = .colorNamed("TxtAlert4") + } + } else { + + // Else show new user style info + bakerIcon.image = UIImage(named: "AlertKnockout")?.withTintColor(.colorNamed("BGB4")) + + bakerButton.setTitle("Start Staking", for: .normal) + bakerLabelRightConstraint.isActive = false + regularlyVotesTitle.isHidden = true + regularlyVotesIcon.isHidden = true + freeSpaceTitleLabel.isHidden = true + freeSpaceValueLabel.isHidden = true + + bakerLabel.text = "No baker chosen" + bakerApyLabel.text = "Delegate and stake your XYZ to participate in on chain governance and earn interest." + } + + } + + @IBAction func changeTapped(_ sender: UIButton) { + self.delegate?.changeTapped() + } + + @IBAction func learnTapped(_ sender: UIButton) { + self.delegate?.learnTapped() + } +} diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib new file mode 100644 index 00000000..c4cba4da --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib @@ -0,0 +1,214 @@ + + + + + + + + + + + + + Figtree-Bold + + + Figtree-SemiBold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell.swift deleted file mode 100644 index 7283b135..00000000 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// TokenDetailsBalanceAndBakerCell.swift -// Kukai Mobile -// -// Created by Simon Mcloughlin on 06/12/2022. -// - -import UIKit - -class TokenDetailsBalanceAndBakerCell: UITableViewCell { - - @IBOutlet weak var tokenIcon: UIImageView! - @IBOutlet weak var balance: UILabel! - @IBOutlet weak var value: UILabel! - @IBOutlet weak var bakerHeading: UILabel? - @IBOutlet weak var bakerButton: CustomisableButton? - - func setup(data: TokenDetailsBalanceAndBakerData) { - if data.isDelegated { - bakerButton?.customButtonType = .none - bakerButton?.setTitle(data.bakerName + " ", for: .normal) - bakerButton?.setTitleColor(.colorNamed("Txt6"), for: .normal) - - } else { - bakerButton?.customButtonType = .secondary - } - - if data.isStaked { - bakerHeading?.text = "Staked" - } else { - bakerHeading?.text = data.isDelegated ? "Delegated" : "Not Delegated" - } - - balance.text = data.balance - balance.accessibilityIdentifier = "token-detials-balance" - value.text = data.value - value.accessibilityIdentifier = "token-detials-balance-value" - bakerButton?.accessibilityIdentifier = "token-detials-baker-button" - } -} diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nobaker.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nobaker.xib deleted file mode 100644 index e9da62b7..00000000 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nobaker.xib +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - Figtree-Bold - - - Figtree-SemiBold - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nostaking.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nostaking.xib deleted file mode 100644 index 20c74cc1..00000000 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_nostaking.xib +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - Figtree-Bold - - - Figtree-SemiBold - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift new file mode 100644 index 00000000..355bfb14 --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift @@ -0,0 +1,30 @@ +// +// TokenDetailsBalanceAndBakerCell.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 06/12/2022. +// + +import UIKit +import KukaiCoreSwift + +class TokenDetailsBalanceAndBakerCell: UITableViewCell { + + @IBOutlet weak var tokenIcon: UIImageView! + @IBOutlet weak var balance: UILabel! + @IBOutlet weak var value: UILabel! + @IBOutlet weak var availableBalance: UILabel! + @IBOutlet weak var availableValue: UILabel! + + func setup(data: TokenDetailsBalanceData) { + balance.text = data.balance + balance.accessibilityIdentifier = "token-detials-balance" + value.text = data.value + value.accessibilityIdentifier = "token-detials-balance-value" + + availableBalance.text = data.availableBalance + availableBalance.accessibilityIdentifier = "token-detials-available-balance" + availableValue.text = data.availableValue + availableValue.accessibilityIdentifier = "token-detials-available-balance-value" + } +} diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_baker.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib similarity index 70% rename from Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_baker.xib rename to Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib index 7d7b351d..fdd034c5 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceAndBakerCell_baker.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib @@ -1,9 +1,9 @@ - + - + @@ -26,47 +26,29 @@ - - - - + + + + + + - + + @@ -113,24 +111,12 @@ - - - - - - - - - - - - diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift index a70b7b02..b900183a 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift @@ -46,14 +46,12 @@ struct TokenDetailsButtonData: Hashable, Identifiable { let hasMoreButton: Bool } -struct TokenDetailsBalanceAndBakerData: Hashable, Identifiable { +struct TokenDetailsBalanceData: Hashable, Identifiable { let id = UUID() let balance: String let value: String - let isDelegationPossible: Bool - let isDelegated: Bool - let isStaked: Bool - let bakerName: String + let availableBalance: String + let availableValue: String } struct TokenDetailsSendData: Hashable { @@ -61,6 +59,15 @@ struct TokenDetailsSendData: Hashable { var isDisabled: Bool } +struct TokenDetailsBakerData: Hashable { + let bakerIcon: URL? + let bakerName: String? + let bakerApy: Decimal + let regularlyVotes: Bool + let freeSpace: Decimal + let enoughSpaceForBalance: Bool +} + struct TokenDetailsActivityHeader: Hashable, Identifiable { let id = UUID() let header: Bool From 922f847af122ac29dee1b262f995480f33316367 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Fri, 15 Nov 2024 09:57:43 +0000 Subject: [PATCH 03/22] WIP: - add new cells, tweak structure --- Kukai Mobile.xcodeproj/project.pbxproj | 8 + .../Account/Cells/TokenDetailsBakerCell.xib | 26 +-- .../Account/Cells/TokenDetailsBalanceCell.xib | 2 +- .../Cells/TokenDetailsStakeBalanceCell.swift | 57 ++++++ .../Cells/TokenDetailsStakeBalanceCell.xib | 185 ++++++++++++++++++ .../Account/TokenDetailsViewModel.swift | 85 +++++--- 6 files changed, 314 insertions(+), 49 deletions(-) create mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.swift create mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.xib diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index 1ae9125d..1a59b988 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -146,6 +146,8 @@ C04B20A52930E1AF00C008CD /* ActivityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04B20A42930E1AF00C008CD /* ActivityService.swift */; }; C04D98562CE64938009491BD /* TokenDetailsBakerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04D98542CE64938009491BD /* TokenDetailsBakerCell.swift */; }; C04D98572CE64938009491BD /* TokenDetailsBakerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C04D98552CE64938009491BD /* TokenDetailsBakerCell.xib */; }; + C04D985A2CE65884009491BD /* TokenDetailsStakeBalanceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04D98582CE65884009491BD /* TokenDetailsStakeBalanceCell.swift */; }; + C04D985B2CE65884009491BD /* TokenDetailsStakeBalanceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C04D98592CE65884009491BD /* TokenDetailsStakeBalanceCell.xib */; }; C04E0EB22ADE7EE7001BF56F /* UpdateWarningCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E0EB12ADE7EE7001BF56F /* UpdateWarningCell.swift */; }; C04E0EB42ADE9216001BF56F /* RequiredUpdateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E0EB32ADE9216001BF56F /* RequiredUpdateViewController.swift */; }; C04E0EB62ADE92CA001BF56F /* AppUpdateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E0EB52ADE92CA001BF56F /* AppUpdateService.swift */; }; @@ -570,6 +572,8 @@ C04B20A42930E1AF00C008CD /* ActivityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityService.swift; sourceTree = ""; }; C04D98542CE64938009491BD /* TokenDetailsBakerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsBakerCell.swift; sourceTree = ""; }; C04D98552CE64938009491BD /* TokenDetailsBakerCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsBakerCell.xib; sourceTree = ""; }; + C04D98582CE65884009491BD /* TokenDetailsStakeBalanceCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsStakeBalanceCell.swift; sourceTree = ""; }; + C04D98592CE65884009491BD /* TokenDetailsStakeBalanceCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsStakeBalanceCell.xib; sourceTree = ""; }; C04E0EB12ADE7EE7001BF56F /* UpdateWarningCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateWarningCell.swift; sourceTree = ""; }; C04E0EB32ADE9216001BF56F /* RequiredUpdateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequiredUpdateViewController.swift; sourceTree = ""; }; C04E0EB52ADE92CA001BF56F /* AppUpdateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateService.swift; sourceTree = ""; }; @@ -1649,6 +1653,8 @@ C01A6348297847AA00278689 /* TokenDetailsSendCell.xib */, C04D98542CE64938009491BD /* TokenDetailsBakerCell.swift */, C04D98552CE64938009491BD /* TokenDetailsBakerCell.xib */, + C04D98582CE65884009491BD /* TokenDetailsStakeBalanceCell.swift */, + C04D98592CE65884009491BD /* TokenDetailsStakeBalanceCell.xib */, C04E2867293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift */, C01A634A297847FB00278689 /* TokenDetailsStakingRewardsCell.xib */, C04E2869293F950900DC4171 /* TokenDetailsActivityHeaderCell.swift */, @@ -1821,6 +1827,7 @@ C0EA19C129096D9400E6B40D /* CollectibleDetailImageCell.xib in Resources */, C008B9BB2965D2CA00B17D96 /* CollectibleDetailAVCell.xib in Resources */, C05B0A302A03F9E3005AA803 /* CollectiblesCollectionHeaderSmallCell.xib in Resources */, + C04D985B2CE65884009491BD /* TokenDetailsStakeBalanceCell.xib in Resources */, C05B0A382A03FC6D005AA803 /* CollectiblesCollectionItemLargeWithTextCell.xib in Resources */, C01A6355297848B200278689 /* TokenDetailsActivityHeaderCell_footer.xib in Resources */, C01372AF29B206E00083E297 /* MenuChoiceCell.xib in Resources */, @@ -1986,6 +1993,7 @@ C02A4FDA27DA421100F42DE4 /* Date+extensions.swift in Sources */, C0248CEE2AA21B5900B1F63C /* String+extensions_shared.swift in Sources */, C003CECD27FDED5D00F64B4C /* CKRecord+extensions.swift in Sources */, + C04D985A2CE65884009491BD /* TokenDetailsStakeBalanceCell.swift in Sources */, C06AEDFD2C00997E005CFDAA /* AddAccountViewController.swift in Sources */, C04E2864293F94C500DC4171 /* TokenDetailsBalanceCell.swift in Sources */, C044E1312AAF4CB40085652F /* SideMenuSettingsViewModel.swift in Sources */, diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib index c4cba4da..6e292ce2 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib @@ -19,7 +19,7 @@ - + @@ -171,27 +171,6 @@ - @@ -204,9 +183,6 @@ - - - diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib index fdd034c5..a12d5e02 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib @@ -18,7 +18,7 @@ - + diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.swift new file mode 100644 index 00000000..472f18c5 --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.swift @@ -0,0 +1,57 @@ +// +// TokenDetailsStakeBalanceCell.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 14/11/2024. +// + +import UIKit + +protocol TokenDetailsStakeBalanceDelegate: AnyObject { + func stakeTapped() + func unstakeTapped() + func finalizeTapped() +} + +class TokenDetailsStakeBalanceCell: UITableViewCell { + + @IBOutlet weak var stakedBalanceLabel: UILabel! + @IBOutlet weak var stakedValueLabel: UILabel! + + @IBOutlet weak var finalizeBalanceLabel: UILabel! + @IBOutlet weak var finalizeValueLabel: UILabel! + + @IBOutlet weak var stakeButton: CustomisableButton! + @IBOutlet weak var unstakeButton: CustomisableButton! + @IBOutlet weak var finalizeButton: CustomisableButton! + + public weak var delegate: TokenDetailsStakeBalanceDelegate? = nil + + func setup(data: TokenDetailsStakeData) { + + stakeButton.customButtonType = .secondary + unstakeButton.customButtonType = .secondary + finalizeButton.customButtonType = .secondary + + stakedBalanceLabel.text = data.stakedBalance + stakedValueLabel.text = data.stakedValue + finalizeBalanceLabel.text = data.finalizeBalance + finalizeValueLabel.text = data.finalizeValue + + stakeButton.isEnabled = data.canStake + unstakeButton.isEnabled = data.canUnstake + finalizeButton.isEnabled = data.canFinalize + } + + @IBAction func stakeTapped(_ sender: Any) { + self.delegate?.stakeTapped() + } + + @IBAction func unstakeTapped(_ sender: Any) { + self.delegate?.unstakeTapped() + } + + @IBAction func finalizeTapped(_ sender: Any) { + self.delegate?.finalizeTapped() + } +} diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.xib new file mode 100644 index 00000000..c10eff5d --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.xib @@ -0,0 +1,185 @@ + + + + + + + + + + + + + Figtree-Bold + + + Figtree-SemiBold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift index b900183a..a3ae5ff1 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift @@ -68,6 +68,16 @@ struct TokenDetailsBakerData: Hashable { let enoughSpaceForBalance: Bool } +struct TokenDetailsStakeData: Hashable { + let stakedBalance: String + let stakedValue: String + let finalizeBalance: String + let finalizeValue: String + let canStake: Bool + let canUnstake: Bool + let canFinalize: Bool +} + struct TokenDetailsActivityHeader: Hashable, Identifiable { let id = UUID() let header: Bool @@ -116,8 +126,10 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { var chartData = AllChartData(day: [], week: [], month: [], year: []) var chartDataUnsucessful = false var buttonData: TokenDetailsButtonData? = nil - var balanceAndBakerData: TokenDetailsBalanceAndBakerData? = nil + var balanceData: TokenDetailsBalanceData? = nil var sendData = TokenDetailsSendData(isBuyTez: false, isDisabled: false) + var bakerData: TokenDetailsBakerData? = nil + var stakeData: TokenDetailsStakeData? = nil var stakingRewardLoadingData = LoadingData() var stakingRewardData: AggregateRewardInformation? = nil var activityHeaderData = TokenDetailsActivityHeader(header: true) @@ -164,30 +176,30 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { cell.setup(delegate: self, chartController: self.chartController, allChartData: obj) return cell - } else if let obj = item.base as? TokenDetailsBalanceAndBakerData { - let reuse = obj.isDelegationPossible ? (obj.isDelegated ? "TokenDetailsBalanceAndBakerCell_baker" : "TokenDetailsBalanceAndBakerCell_nobaker") : "TokenDetailsBalanceAndBakerCell_nostaking" - - if let cell = tableView.dequeueReusableCell(withIdentifier: reuse, for: indexPath) as? TokenDetailsBalanceAndBakerCell { + } else if let obj = item.base as? TokenDetailsBalanceData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsBalanceCell", for: indexPath) as? TokenDetailsBalanceAndBakerCell { + if let tokenURL = self.tokenHeaderData.tokenURL { + MediaProxyService.load(url: tokenURL, to: cell.tokenIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) - if let tokenURL = self.tokenHeaderData.tokenURL { - MediaProxyService.load(url: tokenURL, to: cell.tokenIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) - - } else { - cell.tokenIcon.image = self.tokenHeaderData.tokenImage - } - - if DependencyManager.shared.selectedWalletMetadata?.isWatchOnly == false { - cell.bakerButton?.addTarget(self.delegate, action: #selector(TokenDetailsViewModelDelegate.setBakerTapped), for: .touchUpInside) - } - cell.setup(data: obj) - - return cell + } else { + cell.tokenIcon.image = self.tokenHeaderData.tokenImage } + + cell.setup(data: obj) + return cell + } else if let obj = item.base as? TokenDetailsSendData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsSendCell", for: indexPath) as? TokenDetailsSendCell { cell.sendButton?.addTarget(self.delegate, action: #selector(TokenDetailsViewModelDelegate.sendTapped), for: .touchUpInside) cell.setup(data: obj) return cell + } else if let obj = item.base as? TokenDetailsBakerData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsBakerCell", for: indexPath) as? TokenDetailsBakerCell { + cell.setup(data: obj) + return cell + + } else if let obj = item.base as? TokenDetailsStakeData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsStakeBalanceCell", for: indexPath) as? TokenDetailsStakeBalanceCell { + cell.setup(data: obj) + return cell + } else if let _ = item.base as? LoadingData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsLoadingCell", for: indexPath) as? TokenDetailsLoadingCell { cell.setup() return cell @@ -233,7 +245,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { var data: [AnyHashableSendable] = [ .init(tokenHeaderData), .init(chartData), - .init(balanceAndBakerData), + .init(balanceData), .init(sendData) ] @@ -328,6 +340,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { self.tokenHeaderData.tokenName = token.symbol let tokenBalance = DependencyManager.shared.coinGeckoService.format(decimal: token.balance.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) + let availableTokenBalance = DependencyManager.shared.coinGeckoService.format(decimal: token.availableBalance.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) if token.isXTZ() { self.tokenHeaderData.tokenImage = UIImage.tezosToken() @@ -340,10 +353,36 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { let account = DependencyManager.shared.balanceService.account let xtzValue = (token.balance as? XTZAmount ?? .zero()) * fiatPerToken let tokenValue = DependencyManager.shared.coinGeckoService.format(decimal: xtzValue, numberStyle: .currency, maximumFractionDigits: 2) - let bakerString = (account.delegate?.alias ?? account.delegate?.address.truncateTezosAddress() ?? "") + " " + + let availableXtzValue = (token.availableBalance as? XTZAmount ?? .zero()) * fiatPerToken + let availableValue = DependencyManager.shared.coinGeckoService.format(decimal: availableXtzValue, numberStyle: .currency, maximumFractionDigits: 2) buttonData = TokenDetailsButtonData(isFavourited: true, canBeUnFavourited: false, isHidden: false, canBeHidden: false, canBePurchased: true, canBeViewedOnline: false, hasMoreButton: false) - balanceAndBakerData = TokenDetailsBalanceAndBakerData(balance: tokenBalance, value: tokenValue, isDelegationPossible: true, isDelegated: (account.delegate != nil), isStaked: account.xtzStakedBalance > .zero(), bakerName: bakerString) + balanceData = TokenDetailsBalanceData(balance: tokenBalance, value: tokenValue, availableBalance: availableTokenBalance, availableValue: availableValue) + + // TODO: fetch baker icon + // TODO: need to fetch bakerAPy + // TODO: need to fetch regularlyVotes + // TODO: need to fetch free space + let bakerString = (account.delegate?.alias ?? account.delegate?.address.truncateTezosAddress() ?? "") + " " + bakerData = TokenDetailsBakerData(bakerIcon: nil, bakerName: bakerString, bakerApy: 0, regularlyVotes: true, freeSpace: 1000, enoughSpaceForBalance: true) + + + let stakeBalance = DependencyManager.shared.coinGeckoService.format(decimal: token.stakedBalance.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) + let stakeXtzValue = (token.stakedBalance as? XTZAmount ?? .zero()) * fiatPerToken + let stakeValue = DependencyManager.shared.coinGeckoService.format(decimal: stakeXtzValue, numberStyle: .currency, maximumFractionDigits: 2) + + let unstakeBalance = DependencyManager.shared.coinGeckoService.format(decimal: token.unstakedBalance.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) + let unstakeXtzValue = (token.unstakedBalance as? XTZAmount ?? .zero()) * fiatPerToken + let unstakeValue = DependencyManager.shared.coinGeckoService.format(decimal: unstakeXtzValue, numberStyle: .currency, maximumFractionDigits: 2) + + // TODO: only do this if relevant + // TODO: come up with logic to dictate if can stake (e.g. is free space, user has more than 1 XTZ, etc) + let canStake = true // is delegate and has funds + let canUnstake = token.stakedBalance > .zero() + let canFinalize = token.unstakedBalance > .zero() + + stakeData = TokenDetailsStakeData(stakedBalance: stakeBalance, stakedValue: stakeValue, finalizeBalance: unstakeBalance, finalizeValue: unstakeValue, canStake: canStake, canUnstake: canUnstake, canFinalize: canFinalize) } else { self.tokenHeaderData.tokenURL = token.thumbnailURL @@ -382,12 +421,12 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { tokenBalanceValueString = DependencyManager.shared.coinGeckoService.format(decimal: xtzPrice, numberStyle: .currency, maximumFractionDigits: 2) } - balanceAndBakerData = TokenDetailsBalanceAndBakerData(balance: tokenBalance, value: tokenBalanceValueString, isDelegationPossible: false, isDelegated: false, isStaked: false, bakerName: "") + //balanceAndBakerData = TokenDetailsBalanceAndBakerData(balance: tokenBalance, value: tokenBalanceValueString, isDelegationPossible: false, isDelegated: false, isStaked: false, bakerName: "") } else { let dashedString = DependencyManager.shared.coinGeckoService.dashedCurrencyString() tokenHeaderData.fiatAmount = dashedString - balanceAndBakerData = TokenDetailsBalanceAndBakerData(balance: tokenBalance, value: dashedString, isDelegationPossible: false, isDelegated: false, isStaked: false, bakerName: "") + //balanceAndBakerData = TokenDetailsBalanceAndBakerData(balance: tokenBalance, value: dashedString, isDelegationPossible: false, isDelegated: false, isStaked: false, bakerName: "") } } } From 2fb10b208a005e10854b99547ab8b26904513499 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Fri, 15 Nov 2024 19:47:25 +0000 Subject: [PATCH 04/22] WIP: - local commit --- .../Account/Cells/TokenDetailsBakerCell.swift | 8 +- .../Account/Cells/TokenDetailsBakerCell.xib | 59 ++--- .../Cells/TokenDetailsBalanceCell.swift | 2 +- .../Account/Cells/TokenDetailsBalanceCell.xib | 3 +- .../Account/Cells/TokenDetailsSendCell.xib | 23 +- .../Account/TokenDetailsViewModel.swift | 238 +++++++++++------- 6 files changed, 194 insertions(+), 139 deletions(-) diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift index 030641dc..fa059990 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift @@ -17,7 +17,6 @@ class TokenDetailsBakerCell: UITableViewCell { @IBOutlet weak var bakerIcon: UIImageView! @IBOutlet weak var bakerLabel: UILabel! - @IBOutlet weak var bakerLabelRightConstraint: NSLayoutConstraint! @IBOutlet weak var bakerApyLabel: UILabel! @IBOutlet weak var regularlyVotesTitle: UILabel! @IBOutlet weak var regularlyVotesIcon: UIImageView! @@ -39,7 +38,6 @@ class TokenDetailsBakerCell: UITableViewCell { MediaProxyService.load(url: data.bakerIcon, to: bakerIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) bakerButton.setTitle("Change Baker", for: .normal) - bakerLabelRightConstraint.isActive = true regularlyVotesTitle.isHidden = false regularlyVotesIcon.isHidden = false freeSpaceTitleLabel.isHidden = false @@ -47,9 +45,10 @@ class TokenDetailsBakerCell: UITableViewCell { bakerLabel.text = bakerName bakerApyLabel.text = "Est APY: \(data.bakerApy.rounded(scale: 2, roundingMode: .bankers))%" - regularlyVotesIcon.image = UIImage.init(named: "Check")?.withTintColor( data.regularlyVotes ? .colorNamed("BGGood4") : .colorNamed("TxtAlert4")) + regularlyVotesIcon.image = UIImage.init(named: "Check") + regularlyVotesIcon.tintColor = data.regularlyVotes ? .colorNamed("BGGood4") : .colorNamed("TxtAlert4") - freeSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.format(decimal: data.freeSpace, numberStyle: .currency, maximumFractionDigits: 0) + freeSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.format(decimal: data.freeSpace, numberStyle: .decimal, maximumFractionDigits: 0) if data.freeSpace > 0 && data.enoughSpaceForBalance { freeSpaceTitleLabel.textColor = .colorNamed("Txt10") freeSpaceValueLabel.textColor = .colorNamed("Txt8") @@ -63,7 +62,6 @@ class TokenDetailsBakerCell: UITableViewCell { bakerIcon.image = UIImage(named: "AlertKnockout")?.withTintColor(.colorNamed("BGB4")) bakerButton.setTitle("Start Staking", for: .normal) - bakerLabelRightConstraint.isActive = false regularlyVotesTitle.isHidden = true regularlyVotesIcon.isHidden = true freeSpaceTitleLabel.isHidden = true diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib index 6e292ce2..88200ca0 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.xib @@ -19,21 +19,21 @@ - - + + - + - + @@ -46,54 +46,44 @@ - - - - - - - + + + + + + + @@ -133,13 +130,14 @@ + - - + + @@ -147,9 +145,7 @@ - - - + @@ -162,14 +158,13 @@ - - + diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift index 355bfb14..e170070d 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.swift @@ -8,7 +8,7 @@ import UIKit import KukaiCoreSwift -class TokenDetailsBalanceAndBakerCell: UITableViewCell { +class TokenDetailsBalanceCell: UITableViewCell { @IBOutlet weak var tokenIcon: UIImageView! @IBOutlet weak var balance: UILabel! diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib index a12d5e02..7945fdd9 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBalanceCell.xib @@ -18,7 +18,7 @@ - + @@ -103,7 +103,6 @@ - diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsSendCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsSendCell.xib index 6edd85bd..f9b204f7 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsSendCell.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsSendCell.xib @@ -1,9 +1,9 @@ - + - + @@ -15,15 +15,15 @@ - - + + - + - + @@ -128,15 +128,16 @@ - + + @@ -164,7 +165,7 @@ - + @@ -175,11 +176,11 @@ - - - + + + diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift index 072740f8..d320c668 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift @@ -241,14 +241,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { return } - // Load instantly - // - token header - // - chart spinner - // - balance / available balance - // - Send button - // - if xtz, spinner - // - else, load activity - + // Immediately load balance, logo, buttons and placeholder chart loadOfflineData(token: token) sendData.isBuyTez = (token.isXTZ() && token.balance == .zero()) sendData.isDisabled = DependencyManager.shared.selectedWalletMetadata?.isWatchOnly ?? false @@ -262,12 +255,13 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { if token.isXTZ() { - // If XTZ and we have a delegate set, then we need to fetch more data before displaying anything else - // Otherwise load the baker onboarding flow + // If XTZ, user has a blance, and we have a delegate set, then we need to fetch more data before displaying anything else + // Otherwise load the baker onboarding flow, if user has a balance if DependencyManager.shared.balanceService.account.delegate != nil { - self.needsToLoadOnlineXTZData = true + self.needsToLoadOnlineXTZData = !sendData.isBuyTez data.append(.init(onlineDataLoading)) - } else { + + } else /* TODO: re-enable if !sendData.isBuyTez*/ { data.append(.init(TokenDetailsBakerData(bakerIcon: nil, bakerName: nil, bakerApy: 0, votingParticipation: [], freeSpace: 0, enoughSpaceForBalance: false) )) } } else { @@ -287,18 +281,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { - // When done - // - kick off chart data fetch - // - replace chart cell when done - // - if xtz, kick off baker, baker rewards, + any other stake data fetch - // - add baker view - // - add stake view - // - add pending unstake view - // - add rewards view - // - add activity - - - // Fetch any required remote data + // After UI is updated, fetch the data for chart and reload that 1 cell loadChartData(token: token) { [weak self] result in guard let self = self else { return } self.initialChartLoad = false @@ -326,6 +309,9 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { } } + + + // At the same time, if we should, load all the other XTZ related content, like baker, staking view, delegation/staking rewards, etc if self.needsToLoadOnlineXTZData { loadOnlineXTZData(token: token) { [weak self] in guard let self = self else { return } @@ -334,7 +320,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { var newData: [AnyHashableSendable] = [.init(self.bakerData), .init(self.stakeData)] /* - pending unstake + TODO: pending unstake */ if let rewardData = rewardData { @@ -494,7 +480,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { } } - + // TODO: cache this and only retrieve no more than once per day // Check voting participation onlineXTZFetchGroup.enter() DependencyManager.shared.tzktClient.checkBakerVoteParticipation(forAddress: delegate.address) {[weak self] result in diff --git a/Kukai Mobile/Modules/Login/LoginViewController.swift b/Kukai Mobile/Modules/Login/LoginViewController.swift index 75bef38c..731db39a 100644 --- a/Kukai Mobile/Modules/Login/LoginViewController.swift +++ b/Kukai Mobile/Modules/Login/LoginViewController.swift @@ -64,6 +64,7 @@ class LoginViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + /* // Hide biometric button if its not enabled, or we are in the middle of the edit passcode flow if isEditPasscodeMode() || CurrentDevice.biometricTypeAuthorized() == .none || StorageService.isBiometricEnabled() == false { useBiometricsButton.isHidden = true @@ -83,6 +84,10 @@ class LoginViewController: UIViewController { // Edit passcode popup self.hiddenTextfield.becomeFirstResponder() } + */ + + // TODO: remove + LoginViewController.reconnectAndDismiss() } override func viewDidDisappear(_ animated: Bool) { diff --git a/Kukai Mobile/Services/CoinGeckoService.swift b/Kukai Mobile/Services/CoinGeckoService.swift index a0c87521..92f0c6b5 100644 --- a/Kukai Mobile/Services/CoinGeckoService.swift +++ b/Kukai Mobile/Services/CoinGeckoService.swift @@ -348,7 +348,7 @@ public class CoinGeckoService { return outputString } - func formatLargeTokenDisplay(_ num: Decimal, decimalPlaces: Int) -> String { + func formatLargeTokenDisplay(_ num: Decimal, decimalPlaces: Int, includeThousand: Bool = false, maximumFractionDigits: Int = 3) -> String { var reducedNumber: Decimal = 0 var reducedNumberSymbol: String? = nil @@ -364,7 +364,11 @@ public class CoinGeckoService { case 1_000_000...: reducedNumber = num / 1_000_000 reducedNumberSymbol = "m" - + + case 1_000... where includeThousand: + reducedNumber = num / 1_000 + reducedNumberSymbol = "k" + case 0...: reducedNumber = num @@ -374,7 +378,7 @@ public class CoinGeckoService { var stringToReturn = "" if let symbol = reducedNumberSymbol { - stringToReturn = format(decimal: reducedNumber, numberStyle: .decimal, maximumFractionDigits: 3) + stringToReturn = format(decimal: reducedNumber, numberStyle: .decimal, maximumFractionDigits: maximumFractionDigits) stringToReturn += symbol } else { From 8bd99562ea2d6dee17ee14061f6d7bfd564569a0 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Wed, 20 Nov 2024 15:24:31 +0000 Subject: [PATCH 07/22] - renamed old files incorrectly using word stake - wired up some new stake buttons - setup watch wallet states - setup ghostnet baker config fetch --- Kukai Mobile.xcodeproj/project.pbxproj | 24 ++--- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../Modules/Account/Account.storyboard | 94 +++---------------- .../Account/Cells/TokenDetailsBakerCell.swift | 1 + .../Cells/TokenDetailsStakeBalanceCell.swift | 6 +- .../Account/TokenDetailsViewController.swift | 39 ++++++-- .../Account/TokenDetailsViewModel.swift | 27 ++++-- .../Modules/Home/Base.lproj/Home.storyboard | 4 +- .../Stake/BakerDetailsViewController.swift | 12 +-- ...ell.swift => ChooseBakerHeadingCell.swift} | 4 +- ....swift => ChooseBakerViewController.swift} | 8 +- ...Model.swift => ChooseBakerViewModel.swift} | 14 +-- .../Stake/ConfirmStakeViewController.swift | 6 +- .../EnterCustomBakerViewController.swift | 4 +- Kukai Mobile/Modules/Stake/Stake.storyboard | 14 +-- 15 files changed, 111 insertions(+), 148 deletions(-) rename Kukai Mobile/Modules/Stake/Cells/{StakeHeadingCell.swift => ChooseBakerHeadingCell.swift} (69%) rename Kukai Mobile/Modules/Stake/{StakeViewController.swift => ChooseBakerViewController.swift} (95%) rename Kukai Mobile/Modules/Stake/{StakeViewModel.swift => ChooseBakerViewModel.swift} (84%) diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index f9292150..bede9881 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -34,7 +34,7 @@ C008D4132A979B5B000B4503 /* AccountButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C008D4122A979B5B000B4503 /* AccountButtonCell.swift */; }; C008D4152A979D22000B4503 /* AccountReceiveAssetsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C008D4142A979D22000B4503 /* AccountReceiveAssetsCell.swift */; }; C008D4172A979D3A000B4503 /* AccountDiscoverCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C008D4162A979D3A000B4503 /* AccountDiscoverCell.swift */; }; - C009308F28CB3DC500763885 /* StakeHeadingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C009308E28CB3DC500763885 /* StakeHeadingCell.swift */; }; + C009308F28CB3DC500763885 /* ChooseBakerHeadingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C009308E28CB3DC500763885 /* ChooseBakerHeadingCell.swift */; }; C009CD832A1CD7C600CFB88C /* Data+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C009CD822A1CD7C600CFB88C /* Data+extensions.swift */; }; C009F88B28195B9C007EA8A8 /* AddressTypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C009F88A28195B9C007EA8A8 /* AddressTypeViewController.swift */; }; C009F88D28197F07007EA8A8 /* TezosDomainValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C009F88C28197F07007EA8A8 /* TezosDomainValidator.swift */; }; @@ -321,8 +321,8 @@ C0BBF17E2C4149D000138C39 /* LookingForDevicesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BBF17D2C4149D000138C39 /* LookingForDevicesViewController.swift */; }; C0BF19A32938FB490044D942 /* TokenContractViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BF19A22938FB490044D942 /* TokenContractViewController.swift */; }; C0C6B5CD28CA25BD00368AEA /* PublicBakerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5CC28CA25BD00368AEA /* PublicBakerCell.swift */; }; - C0C6B5CF28CA27AF00368AEA /* StakeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5CE28CA27AF00368AEA /* StakeViewController.swift */; }; - C0C6B5D128CA2A6B00368AEA /* StakeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5D028CA2A6B00368AEA /* StakeViewModel.swift */; }; + C0C6B5CF28CA27AF00368AEA /* ChooseBakerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5CE28CA27AF00368AEA /* ChooseBakerViewController.swift */; }; + C0C6B5D128CA2A6B00368AEA /* ChooseBakerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5D028CA2A6B00368AEA /* ChooseBakerViewModel.swift */; }; C0C7A0FE2955B8CF00ADCA51 /* NoContactsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C7A0FD2955B8CF00ADCA51 /* NoContactsCell.swift */; }; C0C7DFB829BF34ED00F60E0C /* SideMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0C7DFB729BF34ED00F60E0C /* SideMenu.storyboard */; }; C0C7DFBA29BF37FF00F60E0C /* SideMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C7DFB929BF37FF00F60E0C /* SideMenuViewController.swift */; }; @@ -462,7 +462,7 @@ C008D4122A979B5B000B4503 /* AccountButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountButtonCell.swift; sourceTree = ""; }; C008D4142A979D22000B4503 /* AccountReceiveAssetsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountReceiveAssetsCell.swift; sourceTree = ""; }; C008D4162A979D3A000B4503 /* AccountDiscoverCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDiscoverCell.swift; sourceTree = ""; }; - C009308E28CB3DC500763885 /* StakeHeadingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeHeadingCell.swift; sourceTree = ""; }; + C009308E28CB3DC500763885 /* ChooseBakerHeadingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseBakerHeadingCell.swift; sourceTree = ""; }; C009CD822A1CD7C600CFB88C /* Data+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+extensions.swift"; sourceTree = ""; }; C009F88A28195B9C007EA8A8 /* AddressTypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressTypeViewController.swift; sourceTree = ""; }; C009F88C28197F07007EA8A8 /* TezosDomainValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TezosDomainValidator.swift; sourceTree = ""; }; @@ -756,8 +756,8 @@ C0BBF17D2C4149D000138C39 /* LookingForDevicesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LookingForDevicesViewController.swift; sourceTree = ""; }; C0BF19A22938FB490044D942 /* TokenContractViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenContractViewController.swift; sourceTree = ""; }; C0C6B5CC28CA25BD00368AEA /* PublicBakerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicBakerCell.swift; sourceTree = ""; }; - C0C6B5CE28CA27AF00368AEA /* StakeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeViewController.swift; sourceTree = ""; }; - C0C6B5D028CA2A6B00368AEA /* StakeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeViewModel.swift; sourceTree = ""; }; + C0C6B5CE28CA27AF00368AEA /* ChooseBakerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseBakerViewController.swift; sourceTree = ""; }; + C0C6B5D028CA2A6B00368AEA /* ChooseBakerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseBakerViewModel.swift; sourceTree = ""; }; C0C7A0FD2955B8CF00ADCA51 /* NoContactsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoContactsCell.swift; sourceTree = ""; }; C0C7DFB729BF34ED00F60E0C /* SideMenu.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SideMenu.storyboard; sourceTree = ""; }; C0C7DFB929BF37FF00F60E0C /* SideMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuViewController.swift; sourceTree = ""; }; @@ -1507,7 +1507,7 @@ C0C6B5C728CA257500368AEA /* Cells */ = { isa = PBXGroup; children = ( - C009308E28CB3DC500763885 /* StakeHeadingCell.swift */, + C009308E28CB3DC500763885 /* ChooseBakerHeadingCell.swift */, C0C6B5CC28CA25BD00368AEA /* PublicBakerCell.swift */, ); path = Cells; @@ -1602,8 +1602,8 @@ children = ( C0C6B5C728CA257500368AEA /* Cells */, C0F91C6928BFB3200081E8E8 /* Stake.storyboard */, - C0C6B5CE28CA27AF00368AEA /* StakeViewController.swift */, - C0C6B5D028CA2A6B00368AEA /* StakeViewModel.swift */, + C0C6B5CE28CA27AF00368AEA /* ChooseBakerViewController.swift */, + C0C6B5D028CA2A6B00368AEA /* ChooseBakerViewModel.swift */, C0717B1D2A697D3D007F9419 /* EnterCustomBakerViewController.swift */, C034F2C22A6A913B000F4C4C /* ConfirmStakeViewController.swift */, C034F2C42A6AD2E5000F4C4C /* BakerDetailsViewController.swift */, @@ -1904,7 +1904,7 @@ C090A6732B0BA1C000F50C76 /* TokenDetailsHeaderCell.swift in Sources */, C0B372872B0CC13500159266 /* CollectibleDetailQuantityCell.swift in Sources */, C0BB8BA1293F5E6200C0E1DD /* CustomisableButton.swift in Sources */, - C009308F28CB3DC500763885 /* StakeHeadingCell.swift in Sources */, + C009308F28CB3DC500763885 /* ChooseBakerHeadingCell.swift in Sources */, C06E0D5F287C3E02007A580B /* WalletConnectViewController.swift in Sources */, C00D6EB42A2E1B490060812A /* ThemeChoiceCell.swift in Sources */, C0081FD627D8FE2300F7FEFF /* ActivityViewController.swift in Sources */, @@ -2111,7 +2111,7 @@ C049E73C2881838E00887B64 /* AccountsViewModel.swift in Sources */, C083E6572A6EB79300B3BEBE /* URLFromString.swift in Sources */, C049E74028819E3800887B64 /* DiscoverViewModel.swift in Sources */, - C0C6B5CF28CA27AF00368AEA /* StakeViewController.swift in Sources */, + C0C6B5CF28CA27AF00368AEA /* ChooseBakerViewController.swift in Sources */, C03BF88D2A279836003BD343 /* ActivityItemBatchCell.swift in Sources */, C01372B229B206FF0083E297 /* MenuHeaderCell.swift in Sources */, C06AEDFF2C00998D005CFDAA /* AddAccountViewModel.swift in Sources */, @@ -2129,7 +2129,7 @@ C0F5A8DE29ED85580061DBBD /* EditWalletViewController.swift in Sources */, C0FE432E2AD6D44400312940 /* CustomAVPlayerViewController.swift in Sources */, C02F3CB62BFF77A900FA6383 /* AddWalletViewModel.swift in Sources */, - C0C6B5D128CA2A6B00368AEA /* StakeViewModel.swift in Sources */, + C0C6B5D128CA2A6B00368AEA /* ChooseBakerViewModel.swift in Sources */, C031D3E927D114A600EABBE6 /* TokenDetailsViewController.swift in Sources */, C09860AE27C3C26B00F888AF /* SendTokenAmountViewController.swift in Sources */, C0D01CF329AE4A82007A2468 /* WalletConnectService.swift in Sources */, diff --git a/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f84fb87a..2f53b7ef 100644 --- a/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -70,7 +70,7 @@ "location" : "https://github.com/kukai-wallet/kukai-core-swift", "state" : { "branch" : "feature/bakers_and_staking", - "revision" : "0fc79b3e6a64b072851cb21cffa76f75b5218c2c" + "revision" : "eb5084dcf110cc2ad8294b8777e83a5f09fe54be" } }, { diff --git a/Kukai Mobile/Modules/Account/Account.storyboard b/Kukai Mobile/Modules/Account/Account.storyboard index f5ac936c..c8ac2002 100644 --- a/Kukai Mobile/Modules/Account/Account.storyboard +++ b/Kukai Mobile/Modules/Account/Account.storyboard @@ -1,9 +1,9 @@ - + - + @@ -61,13 +61,13 @@ @@ -501,11 +501,11 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + - @@ -553,10 +553,10 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + - + From 72d3f97aa6de6f8bc45d86a775a8523e6a949d83 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Wed, 20 Nov 2024 19:27:27 +0000 Subject: [PATCH 08/22] WIP: committing local changes --- Kukai Mobile.xcodeproj/project.pbxproj | 4 + .../Cells/TokenDetailsStakeBalanceCell.xib | 12 +- .../Modules/Login/LoginViewController.swift | 5 - Kukai Mobile/Modules/Stake/Stake.storyboard | 856 ++++++++++++++++++ .../Stake/StakeAmountViewController.swift | 161 ++++ 5 files changed, 1027 insertions(+), 11 deletions(-) create mode 100644 Kukai Mobile/Modules/Stake/StakeAmountViewController.swift diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index bede9881..0306af26 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -355,6 +355,7 @@ C0DAF38C29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DAF38A29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.swift */; }; C0DAF38D29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0DAF38B29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.xib */; }; C0DB48172785C6DD00D3B4F9 /* FadeSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB48162785C6DD00D3B4F9 /* FadeSegue.swift */; }; + C0DB694B2CEE40D3000C1A17 /* StakeAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */; }; C0E2317D29897BB5007BC79D /* SendTokenConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E2317C29897BB5007BC79D /* SendTokenConfirmViewController.swift */; }; C0E44BAC2A41FDEE00C2A7C0 /* WatchWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E44BAB2A41FDEE00C2A7C0 /* WatchWalletViewController.swift */; }; C0E4EF3E294105E3007C69CA /* TokenDetailsMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E4EF3D294105E3007C69CA /* TokenDetailsMessageCell.swift */; }; @@ -789,6 +790,7 @@ C0DAF38A29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhostnetWarningCollectionViewCell.swift; sourceTree = ""; }; C0DAF38B29F14B85006F05A9 /* GhostnetWarningCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GhostnetWarningCollectionViewCell.xib; sourceTree = ""; }; C0DB48162785C6DD00D3B4F9 /* FadeSegue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadeSegue.swift; sourceTree = ""; }; + C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeAmountViewController.swift; sourceTree = ""; }; C0E2317C29897BB5007BC79D /* SendTokenConfirmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTokenConfirmViewController.swift; sourceTree = ""; }; C0E44BAB2A41FDEE00C2A7C0 /* WatchWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchWalletViewController.swift; sourceTree = ""; }; C0E4EF3D294105E3007C69CA /* TokenDetailsMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsMessageCell.swift; sourceTree = ""; }; @@ -1607,6 +1609,7 @@ C0717B1D2A697D3D007F9419 /* EnterCustomBakerViewController.swift */, C034F2C22A6A913B000F4C4C /* ConfirmStakeViewController.swift */, C034F2C42A6AD2E5000F4C4C /* BakerDetailsViewController.swift */, + C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */, ); path = Stake; sourceTree = ""; @@ -1897,6 +1900,7 @@ C0FA74D82A4AF79300CA845B /* LoadingGroupModeCell.swift in Sources */, C0638AAC2B9B2A22009AA870 /* JailbreakWarningViewController.swift in Sources */, C0FB90442B8375D70032C8CE /* EmptyCollectionCell.swift in Sources */, + C0DB694B2CEE40D3000C1A17 /* StakeAmountViewController.swift in Sources */, C0172A042A98AD5400163179 /* UITableViewCellButtonDelegate.swift in Sources */, C04E0EB42ADE9216001BF56F /* RequiredUpdateViewController.swift in Sources */, C0239BA12886C5C600E0C973 /* DefiViewModel.swift in Sources */, diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.xib index c10eff5d..fee5ae9f 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakeBalanceCell.xib @@ -45,19 +45,19 @@ @@ -1058,6 +1059,846 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1074,9 +1915,15 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + + + + @@ -1086,6 +1933,9 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + @@ -1095,12 +1945,18 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + + + + diff --git a/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift b/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift new file mode 100644 index 00000000..b0603f2e --- /dev/null +++ b/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift @@ -0,0 +1,161 @@ +// +// StakeAmountViewController.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 20/11/2024. +// + +import UIKit +import KukaiCoreSwift + +class StakeAmountViewController: UIViewController { + + @IBOutlet weak var bakerIcon: UIImageView! + @IBOutlet weak var bakerNameLabel: UILabel! + @IBOutlet weak var bakerSplitValueLabel: UILabel! + @IBOutlet weak var bakerSpaceValueLabel: UILabel! + @IBOutlet weak var bakerRewardsValueLabel: UILabel! + + @IBOutlet weak var tokenNameLabel: UILabel! + @IBOutlet weak var tokenBalanceLabel: UILabel! + @IBOutlet weak var tokenIcon: UIImageView! + @IBOutlet weak var tokenSysmbolLabel: UILabel! + @IBOutlet weak var textfield: ValidatorTextField! + @IBOutlet weak var fiatLabel: UILabel! + @IBOutlet weak var maxButton: UIButton! + + @IBOutlet weak var warningLabel: UILabel! + @IBOutlet weak var errorLabel: UILabel! + + @IBOutlet weak var reviewButton: CustomisableButton! + + private var selectedToken: Token? = nil + + override func viewDidLoad() { + super.viewDidLoad() + GradientView.add(toView: self.view, withType: .fullScreenBackground) + + selectedToken = TransactionService.shared.sendData.chosenToken + guard let token = selectedToken else { + self.windowError(withTitle: "error".localized(), description: "error-no-token".localized()) + return + } + + // To section + + + + // Token data + tokenBalanceLabel.text = token.availableBalance.normalisedRepresentation + tokenSysmbolLabel.text = token.symbol + fiatLabel?.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: .zero()) + tokenIcon.addTokenIcon(token: token) + + + // Textfield + textfield.validatorTextFieldDelegate = self + textfield.validator = TokenAmountValidator(balanceLimit: token.availableBalance, decimalPlaces: token.decimalPlaces) + textfield.addDoneToolbar() + textfield.numericAndSeperatorOnly = true + + errorLabel.isHidden = true + warningLabel.isHidden = true + reviewButton.customButtonType = .primary + reviewButton.isEnabled = false + } + + @IBAction func closeTapped(_ sender: Any) { + self.dismissBottomSheet() + } + + @IBAction func reviewTapped(_ sender: Any) { + self.textfield.resignFirstResponder() + estimateFeeAndNavigate() + } + + @IBAction func maxTapped(_ sender: Any) { + textfield.text = ((selectedToken?.availableBalance ?? .zero()) - XTZAmount(fromNormalisedAmount: 1)).normalisedRepresentation + let _ = textfield.revalidateTextfield() + } + + func estimateFeeAndNavigate() { + guard let destination = TransactionService.shared.sendData.destination, let selectedWalletMetadata = DependencyManager.shared.selectedWalletMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-destination".localized()) + return + } + + if let token = TransactionService.shared.sendData.chosenToken, let amount = TokenAmount(fromNormalisedAmount: textfield.text ?? "", decimalPlaces: token.decimalPlaces) { + self.showLoadingView() + + let operations = OperationFactory.sendOperation(amount, of: token, from: selectedWalletMetadata.address, to: destination) + TransactionService.shared.sendData.chosenAmount = amount + + // Estimate the cost of the operation (ideally display this to a user first and let them confirm) + DependencyManager.shared.tezosNodeClient.estimate(operations: operations, walletAddress: selectedWalletMetadata.address, base58EncodedPublicKey: selectedWalletMetadata.bas58EncodedPublicKey) { [weak self] estimationResult in + + switch estimationResult { + case .success(let estimationResult): + TransactionService.shared.currentOperationsAndFeesData = TransactionService.OperationsAndFeesData(estimatedOperations: estimationResult.operations) + TransactionService.shared.currentForgedString = estimationResult.forgedString + self?.loadingViewHideActivityAndFade() + self?.performSegue(withIdentifier: "confirm", sender: nil) + + case .failure(let estimationError): + self?.hideLoadingView() + self?.windowError(withTitle: "error".localized(), description: estimationError.description) + } + } + } + } +} + +extension StakeAmountViewController: ValidatorTextFieldDelegate { + + func textFieldDidBeginEditing(_ textField: UITextField) { + + } + + func textFieldDidEndEditing(_ textField: UITextField) { + + } + + func textFieldShouldClear(_ textField: UITextField) -> Bool { + return true + } + + func validated(_ validated: Bool, textfield: ValidatorTextField, forText text: String) { + guard let token = TransactionService.shared.sendData.chosenToken else { + return + } + + if validated, let textDecimal = Decimal(string: text) { + self.errorLabel.isHidden = true + self.validateMaxXTZ(input: text) + self.fiatLabel?.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: TokenAmount(fromNormalisedAmount: textDecimal, decimalPlaces: token.decimalPlaces)) + self.reviewButton.isEnabled = true + + } else if text != "" { + errorLabel.isHidden = false + self.fiatLabel?.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: .zero()) + self.reviewButton.isEnabled = false + self.warningLabel.isHidden = true + + } else { + self.errorLabel.isHidden = true + self.warningLabel.isHidden = true + self.fiatLabel?.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: .zero()) + } + } + + func doneOrReturnTapped(isValid: Bool, textfield: ValidatorTextField, forText text: String?) { + + } + + func validateMaxXTZ(input: String) { + if selectedToken?.isXTZ() == true, let balance = selectedToken?.availableBalance, let inputAmount = XTZAmount(fromNormalisedAmount: input, decimalPlaces: 6), balance == inputAmount { + warningLabel.isHidden = false + } else { + warningLabel.isHidden = true + } + } +} From 35e6a1bd15b899127e5f7d6f7b0a4796b9e60454 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Wed, 20 Nov 2024 20:47:42 +0000 Subject: [PATCH 09/22] wire up stake amount screen --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../Modules/Account/Account.storyboard | 13 ++++++-- .../Account/TokenDetailsViewController.swift | 14 ++++---- .../Account/TokenDetailsViewModel.swift | 6 ++-- Kukai Mobile/Modules/Stake/Stake.storyboard | 7 ++-- .../Stake/StakeAmountViewController.swift | 32 ++++++++++++++----- .../Services/TransactionService.swift | 26 +++++++++++++++ 7 files changed, 75 insertions(+), 25 deletions(-) diff --git a/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2f53b7ef..3d87d818 100644 --- a/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -70,7 +70,7 @@ "location" : "https://github.com/kukai-wallet/kukai-core-swift", "state" : { "branch" : "feature/bakers_and_staking", - "revision" : "eb5084dcf110cc2ad8294b8777e83a5f09fe54be" + "revision" : "67a239a5eccfb981cdfe7291ca495b90f4b65256" } }, { diff --git a/Kukai Mobile/Modules/Account/Account.storyboard b/Kukai Mobile/Modules/Account/Account.storyboard index c8ac2002..465da38e 100644 --- a/Kukai Mobile/Modules/Account/Account.storyboard +++ b/Kukai Mobile/Modules/Account/Account.storyboard @@ -1417,7 +1417,8 @@ - + + @@ -1800,7 +1801,7 @@ - + @@ -1842,6 +1843,14 @@ The estimate uses exchange data that might not be up to date and does not take i + + + + + + + + diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift index ced56459..66c59291 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift @@ -211,28 +211,30 @@ extension TokenDetailsViewController: TokenDetailsBakerDelegate { func changeTapped() { if viewModel.isNewBakerFlow() { - + self.alert(errorWithMessage: "Under Construction") } else { - self.performSegue(withIdentifier: "stake", sender: nil) + self.performSegue(withIdentifier: "choose-baker", sender: nil) } } func learnTapped() { - + self.alert(errorWithMessage: "Under Construction") } } extension TokenDetailsViewController: TokenDetailsStakeBalanceDelegate { func stakeTapped() { - + TransactionService.shared.stakeData.chosenBaker = viewModel.baker + TransactionService.shared.stakeData.chosenToken = viewModel.token + self.performSegue(withIdentifier: "stake-amount", sender: nil) } func unstakeTapped() { - + self.alert(errorWithMessage: "Under Construction") } func finalizeTapped() { - + self.alert(errorWithMessage: "Under Construction") } } diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift index 7e8bf7dc..a590edbe 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift @@ -116,6 +116,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { weak var delegate: (TokenDetailsViewModelDelegate & TokenDetailsBakerDelegate & TokenDetailsStakeBalanceDelegate)? = nil var token: Token? = nil + var baker: TzKTBaker? = nil var tokenFiatPrice = "" var needsToLoadOnlineXTZData = false @@ -442,7 +443,6 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { let isWatchWallet = DependencyManager.shared.selectedWalletMetadata?.isWatchOnly ?? false let account = DependencyManager.shared.balanceService.account - var baker: TzKTBaker? = nil var votingParticipation: [Bool] = [] @@ -456,7 +456,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { } // TODO: add baker voting to query - baker = res + self?.baker = res self?.onlineXTZFetchGroup.leave() } @@ -504,7 +504,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { // Fire completion when everything is done onlineXTZFetchGroup.notify(queue: .global(qos: .background)) { [weak self] in - guard let baker = baker else { + guard let baker = self?.baker else { // TODO: handle error DispatchQueue.main.async { completion() } return diff --git a/Kukai Mobile/Modules/Stake/Stake.storyboard b/Kukai Mobile/Modules/Stake/Stake.storyboard index a44e93b3..e207f181 100644 --- a/Kukai Mobile/Modules/Stake/Stake.storyboard +++ b/Kukai Mobile/Modules/Stake/Stake.storyboard @@ -1105,7 +1105,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1364,7 +1364,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - - - - + + + + @@ -591,25 +556,25 @@ - + @@ -717,7 +682,7 @@ - + - + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + - + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - - + + + - - - - - + + + + + + + + + + + - - - - - - - + + + + + + + + + + + @@ -1580,37 +1330,26 @@ + + - - - - - - - - - - - + - - - - - - - - - - + + + + + + + - + @@ -1620,119 +1359,84 @@ - + - - + + - - + + - - + + + + + + + + + - - - + + - + + - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + - + + + + + + + + - - - - + + + + @@ -1748,7 +1452,7 @@ - + @@ -1761,25 +1465,25 @@ - + - + - + - + - + - + - + - + @@ -1970,15 +1674,15 @@ - - + + - + + - - - + + @@ -1991,11 +1695,11 @@ - - + + @@ -2030,9 +1734,9 @@ - - - + + + @@ -2041,11 +1745,8 @@ - - - @@ -2106,7 +1807,7 @@ - + @@ -2158,124 +1859,65 @@ - - + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - - + + + - - - - - + + + + + + + + + + - - - - + + + + + + @@ -2291,119 +1933,83 @@ - + - + - - + + - - + + + + + + + + + - - - + + - + + - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + - + + + + + + + + - - - - + + + + @@ -2451,16 +2057,16 @@ - + - + @@ -2493,7 +2099,7 @@ - + @@ -2744,24 +2350,18 @@ - - - - - - - + + + + - - - - - - + + + @@ -4610,9 +4210,6 @@ - - - diff --git a/Kukai Mobile/Modules/Send/SendCollectibleAmountViewController.swift b/Kukai Mobile/Modules/Send/SendCollectibleAmountViewController.swift index 2a6476e4..ad34523f 100644 --- a/Kukai Mobile/Modules/Send/SendCollectibleAmountViewController.swift +++ b/Kukai Mobile/Modules/Send/SendCollectibleAmountViewController.swift @@ -12,13 +12,10 @@ import KukaiCoreSwift class SendCollectibleAmountViewController: UIViewController { @IBOutlet weak var scrollView: UIScrollView! - @IBOutlet weak var toStackViewSocial: UIStackView! - @IBOutlet weak var toStackViewRegular: UIStackView! @IBOutlet weak var addressIcon: UIImageView! @IBOutlet weak var addressAliasLabel: UILabel! @IBOutlet weak var addressLabel: UILabel! - @IBOutlet weak var regularAddressLabel: UILabel! @IBOutlet weak var collectibleImage: UIImageView! @IBOutlet weak var collectibleName: UILabel! @@ -46,14 +43,14 @@ class SendCollectibleAmountViewController: UIViewController { // To section if let alias = TransactionService.shared.sendData.destinationAlias { - toStackViewRegular.isHidden = true - addressAliasLabel.text = alias addressIcon.image = TransactionService.shared.sendData.destinationIcon + addressAliasLabel.text = alias addressLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() } else { - toStackViewSocial.isHidden = true - regularAddressLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() + addressIcon.image = TransactionService.shared.sendData.destinationIcon + addressAliasLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() + addressLabel.isHidden = true } diff --git a/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift index e73f9eeb..94f64b24 100644 --- a/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift @@ -23,14 +23,9 @@ class SendCollectibleConfirmViewController: SendAbstractConfirmViewController, S // From @IBOutlet weak var fromContainer: UIView! - - @IBOutlet weak var fromStackViewSocial: UIStackView! - @IBOutlet weak var fromSocialIcon: UIImageView! - @IBOutlet weak var fromSocialAlias: UILabel! - @IBOutlet weak var fromSocialAddress: UILabel! - - @IBOutlet weak var fromStackViewRegular: UIStackView! - @IBOutlet weak var fromRegularAddress: UILabel! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! // Send @IBOutlet weak var collectibleImage: UIImageView! @@ -40,13 +35,9 @@ class SendCollectibleConfirmViewController: SendAbstractConfirmViewController, S @IBOutlet weak var quantityStackView: UIStackView! @IBOutlet weak var collectibleQuantityLabel: UILabel! - @IBOutlet weak var toStackViewSocial: UIStackView! - @IBOutlet weak var socialIcon: UIImageView! - @IBOutlet weak var socialAlias: UILabel! - @IBOutlet weak var socialAddress: UILabel! - - @IBOutlet weak var toStackViewRegular: UIStackView! - @IBOutlet weak var regularAddress: UILabel! + @IBOutlet weak var toIcon: UIImageView! + @IBOutlet weak var toAlias: UILabel! + @IBOutlet weak var toAddress: UILabel! @IBOutlet weak var feeValueLabel: UILabel! @IBOutlet weak var feeButton: CustomisableButton! @@ -89,17 +80,6 @@ class SendCollectibleConfirmViewController: SendAbstractConfirmViewController, S connectedAppURL = smallIconURL } - let media = TransactionService.walletMedia(forWalletMetadata: walletMetadataForRequestedAccount, ofSize: .size_22) - if let subtitle = media.subtitle { - fromStackViewRegular.isHidden = true - fromSocialAlias.text = media.title - fromSocialIcon.image = media.image - fromSocialAddress.text = subtitle - } else { - fromStackViewSocial.isHidden = true - fromRegularAddress.text = media.title - } - } else { self.isWalletConnectOp = false self.currentSendData = TransactionService.shared.sendData @@ -115,18 +95,35 @@ class SendCollectibleConfirmViewController: SendAbstractConfirmViewController, S updateAmountDisplay() + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true + } + + // Destination view configuration if let alias = currentSendData.destinationAlias { - // social display - toStackViewRegular.isHidden = true - socialAlias.text = alias - socialIcon.image = currentSendData.destinationIcon - socialAddress.text = currentSendData.destination?.truncateTezosAddress() + toIcon.image = currentSendData.destinationIcon + toAlias.text = alias + toAddress.text = currentSendData.destination?.truncateTezosAddress() } else { - // basic display - toStackViewSocial.isHidden = true - regularAddress.text = currentSendData.destination?.truncateTezosAddress() + toIcon.image = currentSendData.destinationIcon + toAlias.text = currentSendData.destination?.truncateTezosAddress() + toAddress.isHidden = true } diff --git a/Kukai Mobile/Modules/Send/SendTokenAmountViewController.swift b/Kukai Mobile/Modules/Send/SendTokenAmountViewController.swift index 4686e217..7a30b43b 100644 --- a/Kukai Mobile/Modules/Send/SendTokenAmountViewController.swift +++ b/Kukai Mobile/Modules/Send/SendTokenAmountViewController.swift @@ -9,14 +9,10 @@ import UIKit import KukaiCoreSwift class SendTokenAmountViewController: UIViewController { - - @IBOutlet weak var toStackViewSocial: UIStackView! - @IBOutlet weak var toStackViewRegular: UIStackView! @IBOutlet weak var addressIcon: UIImageView! @IBOutlet weak var addressAliasLabel: UILabel! @IBOutlet weak var addressLabel: UILabel! - @IBOutlet weak var regularAddressLabel: UILabel! @IBOutlet weak var balanceLabel: UILabel! @IBOutlet weak var inputContainer: UIView! @@ -44,14 +40,14 @@ class SendTokenAmountViewController: UIViewController { // To section if let alias = TransactionService.shared.sendData.destinationAlias { - toStackViewRegular.isHidden = true - addressAliasLabel.text = alias addressIcon.image = TransactionService.shared.sendData.destinationIcon + addressAliasLabel.text = alias addressLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() } else { - toStackViewSocial.isHidden = true - regularAddressLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() + addressIcon.image = TransactionService.shared.sendData.destinationIcon + addressAliasLabel.text = TransactionService.shared.sendData.destination?.truncateTezosAddress() + addressLabel.isHidden = true } diff --git a/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift index d9548e40..6b3edbbc 100644 --- a/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift @@ -24,34 +24,20 @@ class SendTokenConfirmViewController: SendAbstractConfirmViewController, SlideBu // From @IBOutlet weak var fromContainer: UIView! - @IBOutlet weak var fromStackViewSocial: UIStackView! - @IBOutlet weak var fromSocialIcon: UIImageView! - @IBOutlet weak var fromSocialAlias: UILabel! - @IBOutlet weak var fromSocialAddress: UILabel! - - @IBOutlet weak var fromStackViewRegular: UIStackView! - @IBOutlet weak var fromRegularAddress: UILabel! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! // Send - @IBOutlet weak var largeDisplayStackView: UIStackView! - @IBOutlet weak var largeDisplayIcon: UIImageView! - @IBOutlet weak var largeDisplayAmount: UILabel! - @IBOutlet weak var largeDisplaySymbol: UILabel! - @IBOutlet weak var largeDisplayFiat: UILabel! - - @IBOutlet weak var smallDisplayStackView: UIStackView! - @IBOutlet weak var smallDisplayIcon: UIImageView! - @IBOutlet weak var smallDisplayAmount: UILabel! - @IBOutlet weak var smallDisplayFiat: UILabel! + @IBOutlet weak var tokenIcon: UIImageView! + @IBOutlet weak var tokenAmount: UILabel! + @IBOutlet weak var tokenSymbol: UILabel! + @IBOutlet weak var tokenFiat: UILabel! // To - @IBOutlet weak var toStackViewSocial: UIStackView! - @IBOutlet weak var toSocialIcon: UIImageView! - @IBOutlet weak var toSocialAlias: UILabel! - @IBOutlet weak var toSocialAddress: UILabel! - - @IBOutlet weak var toStackViewRegular: UIStackView! - @IBOutlet weak var toRegularAddress: UILabel! + @IBOutlet weak var toIcon: UIImageView! + @IBOutlet weak var toAlias: UILabel! + @IBOutlet weak var toAddress: UILabel! // Fee @IBOutlet weak var feeValueLabel: UILabel! @@ -97,17 +83,6 @@ class SendTokenConfirmViewController: SendAbstractConfirmViewController, SlideBu connectedAppURL = smallIconURL } - let media = TransactionService.walletMedia(forWalletMetadata: walletMetadataForRequestedAccount, ofSize: .size_22) - if let subtitle = media.subtitle { - fromStackViewRegular.isHidden = true - fromSocialAlias.text = media.title - fromSocialIcon.image = media.image - fromSocialAddress.text = subtitle - } else { - fromStackViewSocial.isHidden = true - fromRegularAddress.text = media.title - } - } else { self.isWalletConnectOp = false self.currentSendData = TransactionService.shared.sendData @@ -115,22 +90,38 @@ class SendTokenConfirmViewController: SendAbstractConfirmViewController, SlideBu connectedAppMetadataStackView.isHidden = true connectedAppLabel.isHidden = true - fromContainer.isHidden = true + } + + + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true } // Destination view configuration if let alias = currentSendData.destinationAlias { - // social display - toStackViewRegular.isHidden = true - toSocialAlias.text = alias - toSocialIcon.image = currentSendData.destinationIcon - toSocialAddress.text = currentSendData.destination?.truncateTezosAddress() + toIcon.image = currentSendData.destinationIcon + toAlias.text = alias + toAddress.text = currentSendData.destination?.truncateTezosAddress() } else { - // basic display - toStackViewSocial.isHidden = true - toRegularAddress.text = currentSendData.destination?.truncateTezosAddress() + toIcon.image = currentSendData.destinationIcon + toAlias.text = currentSendData.destination?.truncateTezosAddress() + toAddress.isHidden = true } @@ -209,40 +200,18 @@ class SendTokenConfirmViewController: SendAbstractConfirmViewController, SlideBu func updateAmountDisplay(withValue value: TokenAmount) { guard let token = currentSendData.chosenToken else { - largeDisplayStackView.isHidden = true - smallDisplayIcon.image = UIImage.unknownToken() - smallDisplayAmount.text = "0" - smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) + tokenIcon.image = UIImage.unknownToken() + tokenAmount.text = "0" + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) return } - let approxSizeOfOccupiedSpace: CGFloat = 180 // Yes "magic number", deal with it, very unique business logic at play - let remainder = UIScreen.main.bounds.width - approxSizeOfOccupiedSpace let amountText = DependencyManager.shared.coinGeckoService.format(decimal: value.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: value.decimalPlaces) - var amountWidth = amountText.widthOfString(usingFont: largeDisplayAmount.font) - amountWidth.round(.up) - - var symbolWidth = token.symbol.widthOfString(usingFont: largeDisplaySymbol.font) - symbolWidth.round(.up) - - if (amountWidth + symbolWidth) > remainder { - - // Display with more room for long length numbers - largeDisplayStackView.isHidden = true - smallDisplayIcon.addTokenIcon(token: token) - smallDisplayAmount.text = amountText + " " + token.symbol - smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) - } else { - - // Display with less room and more detail - smallDisplayStackView.isHidden = true - largeDisplayIcon.addTokenIcon(token: token) - largeDisplayAmount.text = amountText - - largeDisplaySymbol.text = token.symbol - largeDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) - } + tokenIcon.addTokenIcon(token: token) + tokenAmount.text = amountText + tokenSymbol.text = token.symbol + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) } func updateFees(isFirstCall: Bool = false) { diff --git a/Kukai Mobile/Modules/Stake/Stake.storyboard b/Kukai Mobile/Modules/Stake/Stake.storyboard index e207f181..4247b682 100644 --- a/Kukai Mobile/Modules/Stake/Stake.storyboard +++ b/Kukai Mobile/Modules/Stake/Stake.storyboard @@ -1237,9 +1237,6 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - - @@ -1872,23 +2042,32 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - - - - + + + + + - - - - - - + + + + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift b/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift index 30218702..3c759ceb 100644 --- a/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift @@ -161,7 +161,7 @@ extension StakeAmountViewController: ValidatorTextFieldDelegate { } func validateMaxXTZ(input: String) { - if selectedToken?.isXTZ() == true, let balance = selectedToken?.availableBalance, let inputAmount = XTZAmount(fromNormalisedAmount: input, decimalPlaces: 6), balance == inputAmount { + if selectedToken?.isXTZ() == true, let balance = selectedToken?.availableBalance, let inputAmount = XTZAmount(fromNormalisedAmount: input, decimalPlaces: 6), balance == inputAmount { warningLabel.isHidden = false } else { warningLabel.isHidden = true diff --git a/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift new file mode 100644 index 00000000..3dcee648 --- /dev/null +++ b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift @@ -0,0 +1,372 @@ +// +// StakeConfirmViewController.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 21/11/2024. +// + +import UIKit +import KukaiCoreSwift +import ReownWalletKit +import os.log + +class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButtonDelegate, EditFeesViewControllerDelegate { + + @IBOutlet weak var closeButton: CustomisableButton! + + // Connected app + @IBOutlet weak var connectedAppLabel: UILabel! + @IBOutlet weak var connectedAppIcon: UIImageView! + @IBOutlet weak var connectedAppNameLabel: UILabel! + @IBOutlet weak var connectedAppMetadataStackView: UIStackView! + + // Baker + @IBOutlet weak var containerViewBaker: GradientView! + @IBOutlet weak var bakerIcon: UIImageView! + @IBOutlet weak var bakerNameLabel: UILabel! + @IBOutlet weak var bakerSplitValueLabel: UILabel! + @IBOutlet weak var bakerSpaceValueLabel: UILabel! + @IBOutlet weak var bakerRewardsValueLabel: UILabel! + + // Stake + @IBOutlet weak var containerViewStake: GradientView! + @IBOutlet weak var largeDisplayStackView: UIStackView! + @IBOutlet weak var largeDisplayIcon: UIImageView! + @IBOutlet weak var largeDisplayAmount: UILabel! + @IBOutlet weak var largeDisplaySymbol: UILabel! + @IBOutlet weak var largeDisplayFiat: UILabel! + + @IBOutlet weak var smallDisplayStackView: UIStackView! + @IBOutlet weak var smallDisplayIcon: UIImageView! + @IBOutlet weak var smallDisplayAmount: UILabel! + @IBOutlet weak var smallDisplayFiat: UILabel! + + // Fee + @IBOutlet weak var feeValueLabel: UILabel! + @IBOutlet weak var feeButton: CustomisableButton! + @IBOutlet weak var slideErrorStackView: UIStackView! + @IBOutlet weak var errorLabel: UILabel! + @IBOutlet weak var slideButton: SlideButton! + @IBOutlet weak var testnetWarningView: UIView! + + private var selectedToken: Token? = nil + private var selectedBaker: TzKTBaker? = nil + private var isSendingMaxTez = false + + var dimBackground: Bool = true + + override func viewDidLoad() { + super.viewDidLoad() + GradientView.add(toView: self.view, withType: .fullScreenBackground) + + if DependencyManager.shared.currentNetworkType != .ghostnet { + testnetWarningView.isHidden = true + } + + selectedToken = TransactionService.shared.stakeData.chosenToken + selectedBaker = TransactionService.shared.stakeData.chosenBaker + guard let token = selectedToken, let baker = selectedBaker else { + self.windowError(withTitle: "error".localized(), description: "error-no-token".localized()) + self.dismissBottomSheet() + return + } + + + // TODO: add "from" to all confirms + // TODO: put this "handle wallet connect data" stuff in abstract helper method + // TODO: Remove the two types of from (social and normal). We only have 1 type + // TODO: Maybe remove the 2 types of token display, large and small. Need to simplify the shit out of this UI mess + + // Handle wallet connect data + if let currentTopic = TransactionService.shared.walletConnectOperationData.request?.topic, + let session = WalletKit.instance.getSessions().first(where: { $0.topic == currentTopic }) { + + guard let account = WalletConnectService.accountFromRequest(TransactionService.shared.walletConnectOperationData.request), + let walletMetadataForRequestedAccount = DependencyManager.shared.walletList.metadata(forAddress: account) else { + self.windowError(withTitle: "error".localized(), description: "error-no-account".localized()) + self.handleRejection() + return + } + + self.isWalletConnectOp = true + self.currentSendData = TransactionService.shared.walletConnectOperationData.sendData + self.selectedMetadata = walletMetadataForRequestedAccount + self.connectedAppNameLabel.text = session.peer.name + + if let iconString = session.peer.icons.first, let iconUrl = URL(string: iconString) { + let smallIconURL = MediaProxyService.url(fromUri: iconUrl, ofFormat: MediaProxyService.Format.icon.rawFormat()) + connectedAppURL = smallIconURL + } + + } else { + self.isWalletConnectOp = false + self.currentSendData = TransactionService.shared.sendData + self.selectedMetadata = DependencyManager.shared.selectedWalletMetadata + + connectedAppMetadataStackView.isHidden = true + connectedAppLabel.isHidden = true + } + + + + + + /* + self.currentDelegateData = TransactionService.shared.delegateData + guard let baker = self.currentDelegateData.chosenBaker else { + return + } + + // Handle wallet connect data + if let currentTopic = TransactionService.shared.walletConnectOperationData.request?.topic, + let session = WalletKit.instance.getSessions().first(where: { $0.topic == currentTopic }) { + + guard let account = WalletConnectService.accountFromRequest(TransactionService.shared.walletConnectOperationData.request), + let walletMetadataForRequestedAccount = DependencyManager.shared.walletList.metadata(forAddress: account) else { + self.windowError(withTitle: "error".localized(), description: "error-no-account".localized()) + self.handleRejection() + return + } + + self.isWalletConnectOp = true + self.selectedMetadata = walletMetadataForRequestedAccount + self.connectedAppNameLabel.text = session.peer.name + + if let iconString = session.peer.icons.first, let iconUrl = URL(string: iconString) { + let smallIconURL = MediaProxyService.url(fromUri: iconUrl, ofFormat: MediaProxyService.Format.icon.rawFormat()) + connectedAppURL = smallIconURL + } + + } else { + self.isWalletConnectOp = false + self.selectedMetadata = DependencyManager.shared.selectedWalletMetadata + + connectedAppMetadataStackView.isHidden = true + connectedAppLabel.isHidden = true + } + + + // Baker info config + if self.currentDelegateData.isAdd == true { + confirmBakerRemoveView.isHidden = true + + bakerAddNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() + if baker.name == nil && baker.delegation.fee == 0 && baker.delegation.capacity == 0 && baker.delegation.estimatedApy == 0 { + bakerAddSplitLabel.text = "N/A" + bakerAddSpaceLabel.text = "N/A" + bakerAddEstimatedRewardLabel.text = "N/A" + + } else { + bakerAddSplitLabel.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerAddSpaceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0) + " XTZ" + bakerAddEstimatedRewardLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + } + + } else { + confirmBakerAddView.isHidden = true + bakerRemoveNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() + } + + + // Fees and amount view config + slideErrorStackView.isHidden = true + + slideButton.delegate = self + */ + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + updateFees(isFirstCall: true) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + /* + guard let baker = self.currentDelegateData.chosenBaker else { + self.windowError(withTitle: "error".localized(), description: "error-chosen-baker".localized()) + self.dismissBottomSheet() + return + } + + if let connectedAppURL = connectedAppURL { + MediaProxyService.load(url: connectedAppURL, to: self.connectedAppIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) + } else { + self.connectedAppIcon.image = UIImage.unknownToken() + } + + if self.currentDelegateData.isAdd == true { + MediaProxyService.load(url: baker.logo, to: bakerAddIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) + + } else { + MediaProxyService.load(url: baker.logo, to: bakerRemoveIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) + } + */ + } + + private func selectedOperationsAndFees() -> [KukaiCoreSwift.Operation] { + if isWalletConnectOp { + return TransactionService.shared.currentRemoteOperationsAndFeesData.selectedOperationsAndFees() + + } else { + return TransactionService.shared.currentOperationsAndFeesData.selectedOperationsAndFees() + } + } + + func didCompleteSlide() { + self.blockInteraction(exceptFor: [closeButton]) + self.performAuth() + } + + override func authSuccessful() { + guard let walletAddress = selectedMetadata?.address, let wallet = WalletCacheService().fetchWallet(forAddress: walletAddress) else { + self.unblockInteraction() + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.slideButton.resetSlider() + return + } + + DependencyManager.shared.tezosNodeClient.send(operations: selectedOperationsAndFees(), withWallet: wallet) { [weak self] sendResult in + switch sendResult { + case .success(let opHash): + Logger.app.info("Sent: \(opHash)") + self?.didSend = true + self?.addPendingTransaction(opHash: opHash) + self?.handleApproval(opHash: opHash, slideButton: self?.slideButton) + + case .failure(let sendError): + self?.unblockInteraction() + self?.slideButton?.resetSlider() + + if let message = SendAbstractConfirmViewController.checkForExpectedLedgerErrors(sendError) { + self?.windowError(withTitle: "error".localized(), description: message) + } + } + } + } + + override func authFailure() { + self.unblockInteraction() + self.slideButton.resetSlider() + } + + func updateAmountDisplay(withValue value: TokenAmount) { + guard let token = currentSendData.chosenToken else { + largeDisplayStackView.isHidden = true + smallDisplayIcon.image = UIImage.unknownToken() + smallDisplayAmount.text = "0" + smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) + return + } + + let approxSizeOfOccupiedSpace: CGFloat = 180 // Yes "magic number", deal with it, very unique business logic at play + let remainder = UIScreen.main.bounds.width - approxSizeOfOccupiedSpace + let amountText = DependencyManager.shared.coinGeckoService.format(decimal: value.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: value.decimalPlaces) + + var amountWidth = amountText.widthOfString(usingFont: largeDisplayAmount.font) + amountWidth.round(.up) + + var symbolWidth = token.symbol.widthOfString(usingFont: largeDisplaySymbol.font) + symbolWidth.round(.up) + + if (amountWidth + symbolWidth) > remainder { + + // Display with more room for long length numbers + largeDisplayStackView.isHidden = true + smallDisplayIcon.addTokenIcon(token: token) + smallDisplayAmount.text = amountText + " " + token.symbol + smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) + } else { + + // Display with less room and more detail + smallDisplayStackView.isHidden = true + largeDisplayIcon.addTokenIcon(token: token) + largeDisplayAmount.text = amountText + + largeDisplaySymbol.text = token.symbol + largeDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) + } + } + + /* + func updateFees(isFirstCall: Bool = false) { + let feesAndData = TransactionService.shared.currentOperationsAndFeesData + let fee = (feesAndData.fee + feesAndData.maxStorageCost) + + checkForErrorsAndWarnings(errorStackView: slideErrorStackView, errorLabel: errorLabel, totalFee: fee) + feeValueLabel.text = fee.normalisedRepresentation + " XTZ" + feeButton.setTitle(feesAndData.type.displayName(), for: .normal) + } + */ + + func updateFees(isFirstCall: Bool = false) { + let feesAndData = isWalletConnectOp ? TransactionService.shared.currentRemoteOperationsAndFeesData : TransactionService.shared.currentOperationsAndFeesData + let fee = (feesAndData.fee + feesAndData.maxStorageCost) + + checkForErrorsAndWarnings(errorStackView: slideErrorStackView, errorLabel: errorLabel, totalFee: fee) + feeValueLabel.text = fee.normalisedRepresentation + " XTZ" + feeButton.setTitle(feesAndData.type.displayName(), for: .normal) + + // Sum of send amount + fee is greater than balance, need to adjust send amount + // For safety, don't allow this logic coming from WC2, as its likely the user is communicating with a smart contract that likely won't accept recieving less than expected XTZ + if !isWalletConnectOp, let token = currentSendData.chosenToken, token.isXTZ(), let amount = currentSendData.chosenAmount, (amount + fee) >= token.availableBalance, let oneMutez = XTZAmount(fromRpcAmount: "1") { + let updatedValue = ((token.availableBalance - oneMutez) - fee) + + if updatedValue < .zero() { + updateAmountDisplay(withValue: .zero()) + slideButton.isUserInteractionEnabled = false + slideButton.alpha = 0.6 + + if isFirstCall { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in + self?.windowError(withTitle: "error-funds-title".localized(), description: String.localized("error-funds-body", withArguments: token.availableBalance.normalisedRepresentation, fee.normalisedRepresentation)) + } + } else { + self.windowError(withTitle: "error-funds-title".localized(), description: String.localized("error-funds-body", withArguments: token.availableBalance.normalisedRepresentation, fee.normalisedRepresentation)) + } + + } else { + updateAmountDisplay(withValue: updatedValue) + slideButton.isUserInteractionEnabled = true + slideButton.alpha = 1 + } + + if isWalletConnectOp { + TransactionService.shared.currentRemoteOperationsAndFeesData.updateXTZAmount(to: updatedValue) + } else { + TransactionService.shared.currentOperationsAndFeesData.updateXTZAmount(to: updatedValue) + } + } else { + updateAmountDisplay(withValue: currentSendData.chosenAmount ?? .zero()) + } + } + + @IBAction func closeTapped(_ sender: Any) { + handleRejection(collapseOnly: true) + } + + func addPendingTransaction(opHash: String) { + guard let selectedWalletMetadata = selectedMetadata, let baker = TransactionService.shared.delegateData.chosenBaker else { return } + + let currentOps = selectedOperationsAndFees() + let counter = Decimal(string: currentOps.last?.counter ?? "0") ?? 0 + let addPendingResult = DependencyManager.shared.activityService.addPending(opHash: opHash, type: .delegation, counter: counter, fromWallet: selectedWalletMetadata, newDelegate: TzKTAddress(alias: baker.name, address: baker.address)) + + DependencyManager.shared.activityService.addUniqueAddressToPendingOperation(address: selectedWalletMetadata.address) + Logger.app.info("Recorded pending transaction: \(addPendingResult)") + } +} + +extension StakeConfirmViewController: BottomSheetCustomCalculateProtocol { + + func bottomSheetHeight() -> CGFloat { + viewDidLoad() + + view.setNeedsLayout() + view.layoutIfNeeded() + + return view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + } +} From 2ebfd282a5a39a04a1d0dcf768fda84bfc18a668 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Thu, 21 Nov 2024 15:50:18 +0000 Subject: [PATCH 11/22] migrate send batch and generic confirm to new single component UI --- .../Modules/Send/Base.lproj/Send.storyboard | 579 ++++++------------ .../Send/SendBatchConfirmViewController.swift | 91 +-- ...SendCollectibleConfirmViewController.swift | 1 - .../SendGenericConfirmViewController.swift | 53 +- 4 files changed, 231 insertions(+), 493 deletions(-) diff --git a/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard b/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard index 2cbadb34..03b78cf2 100644 --- a/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard +++ b/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard @@ -1936,10 +1936,10 @@ - + - + @@ -2006,8 +2006,8 @@ - - + + @@ -2057,16 +2057,16 @@ - + - + @@ -2099,7 +2099,7 @@ - + @@ -2453,7 +2453,7 @@ - + @@ -2505,124 +2505,65 @@ - - + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - - + + + - - - - - + + + + + + + + + + - - - - + + + + + + @@ -2638,165 +2579,10 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -2863,16 +2649,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - - - + + @@ -3096,37 +2943,29 @@ - - - - - - - - - - - - + + + + - - - - + + + + - + @@ -3447,7 +3286,7 @@ - + @@ -3499,124 +3338,65 @@ - - + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - - + + + - - - - - + + + + + + + + + + - - - - + + + + + + @@ -3632,7 +3412,7 @@ - + @@ -3672,7 +3452,7 @@ - + @@ -3944,13 +3724,10 @@ - - - - - - - + + + + diff --git a/Kukai Mobile/Modules/Send/SendBatchConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendBatchConfirmViewController.swift index b53f7259..cf0addbb 100644 --- a/Kukai Mobile/Modules/Send/SendBatchConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendBatchConfirmViewController.swift @@ -23,26 +23,15 @@ class SendBatchConfirmViewController: SendAbstractConfirmViewController, SlideBu // From @IBOutlet weak var fromContainer: UIView! - - @IBOutlet weak var fromStackViewSocial: UIStackView! - @IBOutlet weak var fromSocialIcon: UIImageView! - @IBOutlet weak var fromSocialAlias: UILabel! - @IBOutlet weak var fromSocialAddress: UILabel! - - @IBOutlet weak var fromStackViewRegular: UIStackView! - @IBOutlet weak var fromRegularAddress: UILabel! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! // Send - @IBOutlet weak var largeDisplayStackView: UIStackView! - @IBOutlet weak var largeDisplayIcon: UIImageView! - @IBOutlet weak var largeDisplayAmount: UILabel! - @IBOutlet weak var largeDisplaySymbol: UILabel! - @IBOutlet weak var largeDisplayFiat: UILabel! - - @IBOutlet weak var smallDisplayStackView: UIStackView! - @IBOutlet weak var smallDisplayIcon: UIImageView! - @IBOutlet weak var smallDisplayAmount: UILabel! - @IBOutlet weak var smallDisplayFiat: UILabel! + @IBOutlet weak var tokenIcon: UIImageView! + @IBOutlet weak var tokenAmount: UILabel! + @IBOutlet weak var tokenSymbol: UILabel! + @IBOutlet weak var tokenFiat: UILabel! // To @IBOutlet weak var toBatchView: UIView! @@ -93,17 +82,6 @@ class SendBatchConfirmViewController: SendAbstractConfirmViewController, SlideBu connectedAppURL = smallIconURL } - let media = TransactionService.walletMedia(forWalletMetadata: walletMetadataForRequestedAccount, ofSize: .size_22) - if let subtitle = media.subtitle { - fromStackViewRegular.isHidden = true - fromSocialAlias.text = media.title - fromSocialIcon.image = media.image - fromSocialAddress.text = subtitle - } else { - fromStackViewSocial.isHidden = true - fromRegularAddress.text = media.title - } - } else { self.isWalletConnectOp = false self.currentBatchData = TransactionService.shared.batchData @@ -111,7 +89,24 @@ class SendBatchConfirmViewController: SendAbstractConfirmViewController, SlideBu connectedAppMetadataStackView.isHidden = true connectedAppLabel.isHidden = true - fromContainer.isHidden = true + } + + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true } @@ -208,40 +203,16 @@ class SendBatchConfirmViewController: SendAbstractConfirmViewController, SlideBu func updateAmountDisplay() { guard let token = self.currentBatchData.mainDisplayToken, let amount = self.currentBatchData.mainDisplayAmount else { - largeDisplayStackView.isHidden = true - smallDisplayIcon.image = UIImage.unknownToken() - smallDisplayAmount.text = "0" - smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) + tokenIcon.image = UIImage.unknownToken() + tokenAmount.text = "0" + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) return } - let approxSizeOfOccupiedSpace: CGFloat = 180 // Yes "magic number", deal with it, very unique business logic at play - let remainder = UIScreen.main.bounds.width - approxSizeOfOccupiedSpace let amountText = DependencyManager.shared.coinGeckoService.format(decimal: amount.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) - - var amountWidth = amountText.widthOfString(usingFont: largeDisplayAmount.font) - amountWidth.round(.up) - - var symbolWidth = token.symbol.widthOfString(usingFont: largeDisplaySymbol.font) - symbolWidth.round(.up) - - if (amountWidth + symbolWidth) > remainder { - - // Display with more room for long length numbers - largeDisplayStackView.isHidden = true - smallDisplayIcon.addTokenIcon(token: token) - smallDisplayAmount.text = amountText + " " + token.symbol - smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: amount) - } else { - - // Display with less room and more detail - smallDisplayStackView.isHidden = true - largeDisplayIcon.addTokenIcon(token: token) - largeDisplayAmount.text = amountText - - largeDisplaySymbol.text = token.symbol - largeDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: amount) - } + tokenIcon.addTokenIcon(token: token) + tokenAmount.text = amountText + " " + token.symbol + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: amount) } func updateFees(isFirstCall: Bool = false) { diff --git a/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift index 94f64b24..617eca17 100644 --- a/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendCollectibleConfirmViewController.swift @@ -87,7 +87,6 @@ class SendCollectibleConfirmViewController: SendAbstractConfirmViewController, S connectedAppMetadataStackView.isHidden = true connectedAppLabel.isHidden = true - fromContainer.isHidden = true } diff --git a/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift index fb2a4227..3ee8243c 100644 --- a/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift @@ -23,26 +23,9 @@ class SendGenericConfirmViewController: SendAbstractConfirmViewController, Slide // From @IBOutlet weak var fromContainer: UIView! - - @IBOutlet weak var fromStackViewSocial: UIStackView! - @IBOutlet weak var fromSocialIcon: UIImageView! - @IBOutlet weak var fromSocialAlias: UILabel! - @IBOutlet weak var fromSocialAddress: UILabel! - - @IBOutlet weak var fromStackViewRegular: UIStackView! - @IBOutlet weak var fromRegularAddress: UILabel! - - // Send - @IBOutlet weak var largeDisplayStackView: UIStackView! - @IBOutlet weak var largeDisplayIcon: UIImageView! - @IBOutlet weak var largeDisplayAmount: UILabel! - @IBOutlet weak var largeDisplaySymbol: UILabel! - @IBOutlet weak var largeDisplayFiat: UILabel! - - @IBOutlet weak var smallDisplayStackView: UIStackView! - @IBOutlet weak var smallDisplayIcon: UIImageView! - @IBOutlet weak var smallDisplayAmount: UILabel! - @IBOutlet weak var smallDisplayFiat: UILabel! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! // Operation @IBOutlet weak var moreButton: CustomisableButton! @@ -90,17 +73,6 @@ class SendGenericConfirmViewController: SendAbstractConfirmViewController, Slide connectedAppURL = smallIconURL } - let media = TransactionService.walletMedia(forWalletMetadata: walletMetadataForRequestedAccount, ofSize: .size_22) - if let subtitle = media.subtitle { - fromStackViewRegular.isHidden = true - fromSocialAlias.text = media.title - fromSocialIcon.image = media.image - fromSocialAddress.text = subtitle - } else { - fromStackViewSocial.isHidden = true - fromRegularAddress.text = media.title - } - } else { self.isWalletConnectOp = false self.currentSendData = TransactionService.shared.sendData @@ -112,6 +84,25 @@ class SendGenericConfirmViewController: SendAbstractConfirmViewController, Slide } + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true + } + + // Display JSON updateOperationDisplay() From 437757a28e5c071e0429cd400b3c21f89ecd5bd5 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Thu, 21 Nov 2024 16:13:59 +0000 Subject: [PATCH 12/22] - migrate delegate flow to new single component UI - add "from" to delegate flow - rename some files for consistency - remove gradients that don't match other confirmation screen layouts --- Kukai Mobile.xcodeproj/project.pbxproj | 8 +- .../Modules/Send/Base.lproj/Send.storyboard | 10 +- .../SendGenericConfirmViewController.swift | 1 - .../Send/SendTokenConfirmViewController.swift | 1 - ...=> ChooseBakerConfirmViewController.swift} | 45 +- Kukai Mobile/Modules/Stake/Stake.storyboard | 2200 +++++++++++------ 6 files changed, 1479 insertions(+), 786 deletions(-) rename Kukai Mobile/Modules/Stake/{ConfirmStakeViewController.swift => ChooseBakerConfirmViewController.swift} (86%) diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index 9fe1a369..f76d32bf 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -108,7 +108,7 @@ C031D3ED27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C031D3EC27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift */; }; C031D3EF27D114E300EABBE6 /* CollectiblesDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C031D3EE27D114E300EABBE6 /* CollectiblesDetailsViewController.swift */; }; C0321C7D2811792D0013E72B /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0321C7C2811792D0013E72B /* ThemeManager.swift */; }; - C034F2C32A6A913B000F4C4C /* ConfirmStakeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034F2C22A6A913B000F4C4C /* ConfirmStakeViewController.swift */; }; + C034F2C32A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034F2C22A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift */; }; C034F2C52A6AD2E5000F4C4C /* BakerDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034F2C42A6AD2E5000F4C4C /* BakerDetailsViewController.swift */; }; C03646262A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03646242A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.swift */; }; C03646272A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C03646252A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.xib */; }; @@ -537,7 +537,7 @@ C031D3EC27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectiblesDetailsViewModel.swift; sourceTree = ""; }; C031D3EE27D114E300EABBE6 /* CollectiblesDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectiblesDetailsViewController.swift; sourceTree = ""; }; C0321C7C2811792D0013E72B /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; - C034F2C22A6A913B000F4C4C /* ConfirmStakeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmStakeViewController.swift; sourceTree = ""; }; + C034F2C22A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseBakerConfirmViewController.swift; sourceTree = ""; }; C034F2C42A6AD2E5000F4C4C /* BakerDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BakerDetailsViewController.swift; sourceTree = ""; }; C03646242A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectibleDetailCreatorCell.swift; sourceTree = ""; }; C03646252A30D4CD00F3F5C8 /* CollectibleDetailCreatorCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollectibleDetailCreatorCell.xib; sourceTree = ""; }; @@ -1609,7 +1609,7 @@ C0C6B5CE28CA27AF00368AEA /* ChooseBakerViewController.swift */, C0C6B5D028CA2A6B00368AEA /* ChooseBakerViewModel.swift */, C0717B1D2A697D3D007F9419 /* EnterCustomBakerViewController.swift */, - C034F2C22A6A913B000F4C4C /* ConfirmStakeViewController.swift */, + C034F2C22A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift */, C034F2C42A6AD2E5000F4C4C /* BakerDetailsViewController.swift */, C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */, C0DB694C2CEF567A000C1A17 /* StakeConfirmViewController.swift */, @@ -2187,7 +2187,7 @@ C0EA19D629096E3200E6B40D /* CollectibleDetailSendCell.swift in Sources */, C0172A082A98EC6400163179 /* OnrampViewController.swift in Sources */, C0B2D4C02A70108300BC8DFF /* DiscoverFeaturedItemCell.swift in Sources */, - C034F2C32A6A913B000F4C4C /* ConfirmStakeViewController.swift in Sources */, + C034F2C32A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift in Sources */, C0FCBB852B0D03F8001E01F9 /* CollectibleAttributeDetailViewController.swift in Sources */, C025891F292D1154000D59F6 /* HiddenTokenCell.swift in Sources */, C09635D127CE3CF80095EDBF /* UINavigationController+extensions.swift in Sources */, diff --git a/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard b/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard index 03b78cf2..3486bdd5 100644 --- a/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard +++ b/Kukai Mobile/Modules/Send/Base.lproj/Send.storyboard @@ -1359,7 +1359,7 @@ - + @@ -1379,7 +1379,7 @@ - + @@ -1674,14 +1674,14 @@ - + - + - + diff --git a/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift index 3ee8243c..aefc1ea8 100644 --- a/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendGenericConfirmViewController.swift @@ -80,7 +80,6 @@ class SendGenericConfirmViewController: SendAbstractConfirmViewController, Slide connectedAppMetadataStackView.isHidden = true connectedAppLabel.isHidden = true - fromContainer.isHidden = true } diff --git a/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift index 6b3edbbc..58dc008d 100644 --- a/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendTokenConfirmViewController.swift @@ -23,7 +23,6 @@ class SendTokenConfirmViewController: SendAbstractConfirmViewController, SlideBu // From @IBOutlet weak var fromContainer: UIView! - @IBOutlet weak var fromIcon: UIImageView! @IBOutlet weak var fromAlias: UILabel! @IBOutlet weak var fromAddress: UILabel! diff --git a/Kukai Mobile/Modules/Stake/ConfirmStakeViewController.swift b/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift similarity index 86% rename from Kukai Mobile/Modules/Stake/ConfirmStakeViewController.swift rename to Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift index bb974e43..d11a502b 100644 --- a/Kukai Mobile/Modules/Stake/ConfirmStakeViewController.swift +++ b/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift @@ -1,5 +1,5 @@ // -// ConfirmChooseBakerViewController.swift +// ChooseBakerConfirmViewController.swift // Kukai Mobile // // Created by Simon Mcloughlin on 21/07/2023. @@ -10,8 +10,9 @@ import KukaiCoreSwift import ReownWalletKit import os.log -class ConfirmChooseBakerViewController: SendAbstractConfirmViewController, SlideButtonDelegate, EditFeesViewControllerDelegate { +class ChooseBakerConfirmViewController: SendAbstractConfirmViewController, SlideButtonDelegate, EditFeesViewControllerDelegate { + @IBOutlet var scrollView: UIScrollView! @IBOutlet weak var closeButton: CustomisableButton! // Connected app @@ -20,7 +21,13 @@ class ConfirmChooseBakerViewController: SendAbstractConfirmViewController, Slide @IBOutlet weak var connectedAppNameLabel: UILabel! @IBOutlet weak var connectedAppMetadataStackView: UIStackView! - @IBOutlet weak var containerView: GradientView! + // From + @IBOutlet weak var fromContainer: UIView! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! + + // Baker @IBOutlet weak var confirmBakerAddView: UIView! @IBOutlet weak var bakerAddIcon: UIImageView! @IBOutlet weak var bakerAddNameLabel: UILabel! @@ -86,6 +93,25 @@ class ConfirmChooseBakerViewController: SendAbstractConfirmViewController, Slide } + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() + return + } + + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle + } else { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true + } + + // Baker info config if self.currentDelegateData.isAdd == true { confirmBakerRemoveView.isHidden = true @@ -110,14 +136,14 @@ class ConfirmChooseBakerViewController: SendAbstractConfirmViewController, Slide // Fees and amount view config slideErrorStackView.isHidden = true + feeValueLabel.accessibilityIdentifier = "fee-amount" + feeButton.customButtonType = .secondary slideButton.delegate = self } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - containerView.gradientType = .tableViewCell - updateFees(isFirstCall: true) } @@ -215,14 +241,19 @@ class ConfirmChooseBakerViewController: SendAbstractConfirmViewController, Slide } } -extension ConfirmChooseBakerViewController: BottomSheetCustomCalculateProtocol { +extension ChooseBakerConfirmViewController: BottomSheetCustomCalculateProtocol { func bottomSheetHeight() -> CGFloat { viewDidLoad() + scrollView.setNeedsLayout() view.setNeedsLayout() + scrollView.layoutIfNeeded() view.layoutIfNeeded() - return view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + var height = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + height += scrollView.contentSize.height + + return height } } diff --git a/Kukai Mobile/Modules/Stake/Stake.storyboard b/Kukai Mobile/Modules/Stake/Stake.storyboard index 4247b682..e5f40b33 100644 --- a/Kukai Mobile/Modules/Stake/Stake.storyboard +++ b/Kukai Mobile/Modules/Stake/Stake.storyboard @@ -247,8 +247,8 @@ - + @@ -331,7 +331,6 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - @@ -554,36 +553,30 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - + + - - + + - - + + - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - + - - + + @@ -678,60 +621,60 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - + + + + @@ -830,436 +732,28 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + @@ -1464,7 +958,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1760,130 +1254,1275 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + - - - - - + + + + + + + + + + + - + + + + + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - + + - + + + + + + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + - - - + + + + + + + + + + + - + - - + - + - - + - + - - + + @@ -1973,32 +2618,32 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - + + - + - - + + - - - - - - - - - - - + + + + + + + + + + - - + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + - + + + + + From 411fee8ea099c2762db64780882225be6c6c6d6f Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Thu, 21 Nov 2024 17:05:03 +0000 Subject: [PATCH 13/22] - change stake amount to be full screen instead of bottom sheet - wire up end to end staking flow --- .../Modules/Account/Account.storyboard | 8 +- Kukai Mobile/Modules/Stake/Stake.storyboard | 878 +++--------------- .../Stake/StakeConfirmViewController.swift | 183 ++-- 3 files changed, 212 insertions(+), 857 deletions(-) diff --git a/Kukai Mobile/Modules/Account/Account.storyboard b/Kukai Mobile/Modules/Account/Account.storyboard index 465da38e..429c172a 100644 --- a/Kukai Mobile/Modules/Account/Account.storyboard +++ b/Kukai Mobile/Modules/Account/Account.storyboard @@ -1418,7 +1418,7 @@ - + @@ -1846,10 +1846,12 @@ The estimate uses exchange data that might not be up to date and does not take i - + + + - + diff --git a/Kukai Mobile/Modules/Stake/Stake.storyboard b/Kukai Mobile/Modules/Stake/Stake.storyboard index e5f40b33..61d5850d 100644 --- a/Kukai Mobile/Modules/Stake/Stake.storyboard +++ b/Kukai Mobile/Modules/Stake/Stake.storyboard @@ -553,51 +553,16 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + - + - - - - - - - - + @@ -733,13 +698,13 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -847,7 +812,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + + @@ -953,621 +914,12 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -2174,10 +1526,10 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + - + @@ -2185,7 +1537,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - @@ -2690,6 +2116,11 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + + + @@ -2706,9 +2137,6 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - - @@ -2717,7 +2145,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -2725,7 +2153,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -2733,7 +2161,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + diff --git a/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift index 3dcee648..83559c6f 100644 --- a/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift @@ -12,6 +12,7 @@ import os.log class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButtonDelegate, EditFeesViewControllerDelegate { + @IBOutlet var scrollView: UIScrollView! @IBOutlet weak var closeButton: CustomisableButton! // Connected app @@ -20,8 +21,13 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton @IBOutlet weak var connectedAppNameLabel: UILabel! @IBOutlet weak var connectedAppMetadataStackView: UIStackView! + // From + @IBOutlet weak var fromContainer: UIView! + @IBOutlet weak var fromIcon: UIImageView! + @IBOutlet weak var fromAlias: UILabel! + @IBOutlet weak var fromAddress: UILabel! + // Baker - @IBOutlet weak var containerViewBaker: GradientView! @IBOutlet weak var bakerIcon: UIImageView! @IBOutlet weak var bakerNameLabel: UILabel! @IBOutlet weak var bakerSplitValueLabel: UILabel! @@ -29,17 +35,10 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton @IBOutlet weak var bakerRewardsValueLabel: UILabel! // Stake - @IBOutlet weak var containerViewStake: GradientView! - @IBOutlet weak var largeDisplayStackView: UIStackView! - @IBOutlet weak var largeDisplayIcon: UIImageView! - @IBOutlet weak var largeDisplayAmount: UILabel! - @IBOutlet weak var largeDisplaySymbol: UILabel! - @IBOutlet weak var largeDisplayFiat: UILabel! - - @IBOutlet weak var smallDisplayStackView: UIStackView! - @IBOutlet weak var smallDisplayIcon: UIImageView! - @IBOutlet weak var smallDisplayAmount: UILabel! - @IBOutlet weak var smallDisplayFiat: UILabel! + @IBOutlet weak var tokenIcon: UIImageView! + @IBOutlet weak var tokenAmount: UILabel! + @IBOutlet weak var tokenSymbol: UILabel! + @IBOutlet weak var tokenFiat: UILabel! // Fee @IBOutlet weak var feeValueLabel: UILabel! @@ -65,18 +64,12 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton selectedToken = TransactionService.shared.stakeData.chosenToken selectedBaker = TransactionService.shared.stakeData.chosenBaker - guard let token = selectedToken, let baker = selectedBaker else { + guard let baker = selectedBaker else { self.windowError(withTitle: "error".localized(), description: "error-no-token".localized()) self.dismissBottomSheet() return } - - // TODO: add "from" to all confirms - // TODO: put this "handle wallet connect data" stuff in abstract helper method - // TODO: Remove the two types of from (social and normal). We only have 1 type - // TODO: Maybe remove the 2 types of token display, large and small. Need to simplify the shit out of this UI mess - // Handle wallet connect data if let currentTopic = TransactionService.shared.walletConnectOperationData.request?.topic, let session = WalletKit.instance.getSessions().first(where: { $0.topic == currentTopic }) { @@ -89,7 +82,6 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton } self.isWalletConnectOp = true - self.currentSendData = TransactionService.shared.walletConnectOperationData.sendData self.selectedMetadata = walletMetadataForRequestedAccount self.connectedAppNameLabel.text = session.peer.name @@ -100,7 +92,6 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton } else { self.isWalletConnectOp = false - self.currentSendData = TransactionService.shared.sendData self.selectedMetadata = DependencyManager.shared.selectedWalletMetadata connectedAppMetadataStackView.isHidden = true @@ -108,102 +99,62 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton } - - - - /* - self.currentDelegateData = TransactionService.shared.delegateData - guard let baker = self.currentDelegateData.chosenBaker else { + // From + guard let selectedMetadata = selectedMetadata else { + self.windowError(withTitle: "error".localized(), description: "error-no-wallet-short".localized()) + self.dismissBottomSheet() return } - // Handle wallet connect data - if let currentTopic = TransactionService.shared.walletConnectOperationData.request?.topic, - let session = WalletKit.instance.getSessions().first(where: { $0.topic == currentTopic }) { - - guard let account = WalletConnectService.accountFromRequest(TransactionService.shared.walletConnectOperationData.request), - let walletMetadataForRequestedAccount = DependencyManager.shared.walletList.metadata(forAddress: account) else { - self.windowError(withTitle: "error".localized(), description: "error-no-account".localized()) - self.handleRejection() - return - } - - self.isWalletConnectOp = true - self.selectedMetadata = walletMetadataForRequestedAccount - self.connectedAppNameLabel.text = session.peer.name - - if let iconString = session.peer.icons.first, let iconUrl = URL(string: iconString) { - let smallIconURL = MediaProxyService.url(fromUri: iconUrl, ofFormat: MediaProxyService.Format.icon.rawFormat()) - connectedAppURL = smallIconURL - } - + let media = TransactionService.walletMedia(forWalletMetadata: selectedMetadata, ofSize: .size_22) + if let subtitle = media.subtitle { + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.text = subtitle } else { - self.isWalletConnectOp = false - self.selectedMetadata = DependencyManager.shared.selectedWalletMetadata - - connectedAppMetadataStackView.isHidden = true - connectedAppLabel.isHidden = true + fromIcon.image = media.image + fromAlias.text = media.title + fromAddress.isHidden = true } // Baker info config - if self.currentDelegateData.isAdd == true { - confirmBakerRemoveView.isHidden = true - - bakerAddNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() - if baker.name == nil && baker.delegation.fee == 0 && baker.delegation.capacity == 0 && baker.delegation.estimatedApy == 0 { - bakerAddSplitLabel.text = "N/A" - bakerAddSpaceLabel.text = "N/A" - bakerAddEstimatedRewardLabel.text = "N/A" - - } else { - bakerAddSplitLabel.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" - bakerAddSpaceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0) + " XTZ" - bakerAddEstimatedRewardLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" - } + bakerNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() + if baker.name == nil && baker.delegation.fee == 0 && baker.delegation.capacity == 0 && baker.delegation.estimatedApy == 0 { + bakerSplitValueLabel.text = "N/A" + bakerSpaceValueLabel.text = "N/A" + bakerRewardsValueLabel.text = "N/A" } else { - confirmBakerAddView.isHidden = true - bakerRemoveNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() + bakerSplitValueLabel.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0) + " XTZ" + bakerRewardsValueLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" } // Fees and amount view config slideErrorStackView.isHidden = true + feeValueLabel.accessibilityIdentifier = "fee-amount" + feeButton.customButtonType = .secondary slideButton.delegate = self - */ } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - updateFees(isFirstCall: true) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - /* - guard let baker = self.currentDelegateData.chosenBaker else { - self.windowError(withTitle: "error".localized(), description: "error-chosen-baker".localized()) - self.dismissBottomSheet() - return - } - if let connectedAppURL = connectedAppURL { MediaProxyService.load(url: connectedAppURL, to: self.connectedAppIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) } else { self.connectedAppIcon.image = UIImage.unknownToken() } - if self.currentDelegateData.isAdd == true { - MediaProxyService.load(url: baker.logo, to: bakerAddIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) - - } else { - MediaProxyService.load(url: baker.logo, to: bakerRemoveIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) - } - */ + MediaProxyService.load(url: self.selectedBaker?.logo, to: bakerIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) } private func selectedOperationsAndFees() -> [KukaiCoreSwift.Operation] { @@ -253,54 +204,21 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton } func updateAmountDisplay(withValue value: TokenAmount) { - guard let token = currentSendData.chosenToken else { - largeDisplayStackView.isHidden = true - smallDisplayIcon.image = UIImage.unknownToken() - smallDisplayAmount.text = "0" - smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) + guard let token = selectedToken else { + tokenIcon.image = UIImage.unknownToken() + tokenAmount.text = "0" + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: Token.xtz(), ofAmount: TokenAmount.zero()) return } - let approxSizeOfOccupiedSpace: CGFloat = 180 // Yes "magic number", deal with it, very unique business logic at play - let remainder = UIScreen.main.bounds.width - approxSizeOfOccupiedSpace let amountText = DependencyManager.shared.coinGeckoService.format(decimal: value.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: value.decimalPlaces) - var amountWidth = amountText.widthOfString(usingFont: largeDisplayAmount.font) - amountWidth.round(.up) - - var symbolWidth = token.symbol.widthOfString(usingFont: largeDisplaySymbol.font) - symbolWidth.round(.up) - - if (amountWidth + symbolWidth) > remainder { - - // Display with more room for long length numbers - largeDisplayStackView.isHidden = true - smallDisplayIcon.addTokenIcon(token: token) - smallDisplayAmount.text = amountText + " " + token.symbol - smallDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) - } else { - - // Display with less room and more detail - smallDisplayStackView.isHidden = true - largeDisplayIcon.addTokenIcon(token: token) - largeDisplayAmount.text = amountText - - largeDisplaySymbol.text = token.symbol - largeDisplayFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) - } + tokenIcon.addTokenIcon(token: token) + tokenAmount.text = amountText + tokenSymbol.text = token.symbol + tokenFiat.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: value) } - /* - func updateFees(isFirstCall: Bool = false) { - let feesAndData = TransactionService.shared.currentOperationsAndFeesData - let fee = (feesAndData.fee + feesAndData.maxStorageCost) - - checkForErrorsAndWarnings(errorStackView: slideErrorStackView, errorLabel: errorLabel, totalFee: fee) - feeValueLabel.text = fee.normalisedRepresentation + " XTZ" - feeButton.setTitle(feesAndData.type.displayName(), for: .normal) - } - */ - func updateFees(isFirstCall: Bool = false) { let feesAndData = isWalletConnectOp ? TransactionService.shared.currentRemoteOperationsAndFeesData : TransactionService.shared.currentOperationsAndFeesData let fee = (feesAndData.fee + feesAndData.maxStorageCost) @@ -311,8 +229,9 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton // Sum of send amount + fee is greater than balance, need to adjust send amount // For safety, don't allow this logic coming from WC2, as its likely the user is communicating with a smart contract that likely won't accept recieving less than expected XTZ - if !isWalletConnectOp, let token = currentSendData.chosenToken, token.isXTZ(), let amount = currentSendData.chosenAmount, (amount + fee) >= token.availableBalance, let oneMutez = XTZAmount(fromRpcAmount: "1") { - let updatedValue = ((token.availableBalance - oneMutez) - fee) + if !isWalletConnectOp, let token = selectedToken, token.isXTZ(), let amount = TransactionService.shared.stakeData.chosenAmount, (amount + fee) >= token.availableBalance { + let oneTez = XTZAmount(fromNormalisedAmount: 1) + let updatedValue = ((token.availableBalance - oneTez) - fee) if updatedValue < .zero() { updateAmountDisplay(withValue: .zero()) @@ -339,7 +258,7 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton TransactionService.shared.currentOperationsAndFeesData.updateXTZAmount(to: updatedValue) } } else { - updateAmountDisplay(withValue: currentSendData.chosenAmount ?? .zero()) + updateAmountDisplay(withValue: TransactionService.shared.stakeData.chosenAmount ?? .zero()) } } @@ -347,6 +266,7 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton handleRejection(collapseOnly: true) } + // TODO: update pending transaction func addPendingTransaction(opHash: String) { guard let selectedWalletMetadata = selectedMetadata, let baker = TransactionService.shared.delegateData.chosenBaker else { return } @@ -364,9 +284,14 @@ extension StakeConfirmViewController: BottomSheetCustomCalculateProtocol { func bottomSheetHeight() -> CGFloat { viewDidLoad() + scrollView.setNeedsLayout() view.setNeedsLayout() + scrollView.layoutIfNeeded() view.layoutIfNeeded() - return view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + var height = view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height + height += scrollView.contentSize.height + + return height } } From 720f69e6100cc17af6f6ad85c3d01cd4b4f35e0f Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Fri, 22 Nov 2024 16:34:45 +0000 Subject: [PATCH 14/22] - update library for new functionality - add pending unstakes to token details - incorporate pending unstake logic into finalisable amount calc - update rewards cell display to include staking --- Kukai Mobile.xcodeproj/project.pbxproj | 16 ++ .../xcshareddata/swiftpm/Package.resolved | 2 +- .../TokenDetailsActivityHeaderCell.swift | 1 + .../TokenDetailsPendingUnstakeCell.swift | 26 ++++ .../Cells/TokenDetailsPendingUnstakeCell.xib | 113 ++++++++++++++ .../Cells/TokenDetailsSmallHeadingCell.swift | 14 ++ .../Cells/TokenDetailsSmallHeadingCell.xib | 54 +++++++ .../TokenDetailsStakingRewardsCell.swift | 35 +++-- .../Cells/TokenDetailsStakingRewardsCell.xib | 145 +++++++++--------- .../Account/TokenDetailsViewController.swift | 6 +- .../Account/TokenDetailsViewModel.swift | 66 ++++++-- Kukai Mobile/Modules/Stake/Stake.storyboard | 32 ++-- .../Stake/StakeAmountViewController.swift | 29 ++-- .../Stake/StakeConfirmViewController.swift | 17 +- .../Services/TransactionService.swift | 2 + 15 files changed, 436 insertions(+), 122 deletions(-) create mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.swift create mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.xib create mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsSmallHeadingCell.swift create mode 100644 Kukai Mobile/Modules/Account/Cells/TokenDetailsSmallHeadingCell.xib diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index f76d32bf..9a51fbbf 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -357,6 +357,10 @@ C0DB48172785C6DD00D3B4F9 /* FadeSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB48162785C6DD00D3B4F9 /* FadeSegue.swift */; }; C0DB694B2CEE40D3000C1A17 /* StakeAmountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */; }; C0DB694D2CEF567A000C1A17 /* StakeConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB694C2CEF567A000C1A17 /* StakeConfirmViewController.swift */; }; + C0DB69502CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB694E2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.swift */; }; + C0DB69512CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0DB694F2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.xib */; }; + C0DB69542CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DB69522CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.swift */; }; + C0DB69552CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0DB69532CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.xib */; }; C0E2317D29897BB5007BC79D /* SendTokenConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E2317C29897BB5007BC79D /* SendTokenConfirmViewController.swift */; }; C0E44BAC2A41FDEE00C2A7C0 /* WatchWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E44BAB2A41FDEE00C2A7C0 /* WatchWalletViewController.swift */; }; C0E4EF3E294105E3007C69CA /* TokenDetailsMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E4EF3D294105E3007C69CA /* TokenDetailsMessageCell.swift */; }; @@ -793,6 +797,10 @@ C0DB48162785C6DD00D3B4F9 /* FadeSegue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadeSegue.swift; sourceTree = ""; }; C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeAmountViewController.swift; sourceTree = ""; }; C0DB694C2CEF567A000C1A17 /* StakeConfirmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeConfirmViewController.swift; sourceTree = ""; }; + C0DB694E2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsSmallHeadingCell.swift; sourceTree = ""; }; + C0DB694F2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsSmallHeadingCell.xib; sourceTree = ""; }; + C0DB69522CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsPendingUnstakeCell.swift; sourceTree = ""; }; + C0DB69532CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TokenDetailsPendingUnstakeCell.xib; sourceTree = ""; }; C0E2317C29897BB5007BC79D /* SendTokenConfirmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTokenConfirmViewController.swift; sourceTree = ""; }; C0E44BAB2A41FDEE00C2A7C0 /* WatchWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchWalletViewController.swift; sourceTree = ""; }; C0E4EF3D294105E3007C69CA /* TokenDetailsMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsMessageCell.swift; sourceTree = ""; }; @@ -1661,6 +1669,10 @@ C04D98552CE64938009491BD /* TokenDetailsBakerCell.xib */, C04D98582CE65884009491BD /* TokenDetailsStakeBalanceCell.swift */, C04D98592CE65884009491BD /* TokenDetailsStakeBalanceCell.xib */, + C0DB694E2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.swift */, + C0DB694F2CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.xib */, + C0DB69522CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.swift */, + C0DB69532CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.xib */, C04E2867293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift */, C01A634A297847FB00278689 /* TokenDetailsStakingRewardsCell.xib */, C04E2869293F950900DC4171 /* TokenDetailsActivityHeaderCell.swift */, @@ -1832,6 +1844,7 @@ C0C7DFB829BF34ED00F60E0C /* SideMenu.storyboard in Resources */, C0EA19C129096D9400E6B40D /* CollectibleDetailImageCell.xib in Resources */, C008B9BB2965D2CA00B17D96 /* CollectibleDetailAVCell.xib in Resources */, + C0DB69512CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.xib in Resources */, C05B0A302A03F9E3005AA803 /* CollectiblesCollectionHeaderSmallCell.xib in Resources */, C04D985B2CE65884009491BD /* TokenDetailsStakeBalanceCell.xib in Resources */, C05B0A382A03FC6D005AA803 /* CollectiblesCollectionItemLargeWithTextCell.xib in Resources */, @@ -1852,6 +1865,7 @@ C0D6A9342955C231002BE8DA /* SendAddressType.storyboard in Resources */, C050365D271ED8E600E7A664 /* Onboarding.storyboard in Resources */, C000FA532A02A08600A59861 /* CollectiblesCollectionCell.xib in Resources */, + C0DB69552CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.xib in Resources */, C01A63512978481C00278689 /* TokenDetailsLoadingCell.xib in Resources */, C0239B9A2886B92500E0C973 /* Defi.storyboard in Resources */, C0DAF38929F13F6C006F05A9 /* GhostnetWarningCell.xib in Resources */, @@ -1950,6 +1964,7 @@ C0C7DFBF29BF70DD00F60E0C /* SideMenuViewModel.swift in Sources */, C04E2860293F940E00DC4171 /* TokenDetailsChartCell.swift in Sources */, C064D4D32B87B13E008F5947 /* SideMenuLookupViewController.swift in Sources */, + C0DB69542CF0E135000C1A17 /* TokenDetailsPendingUnstakeCell.swift in Sources */, C06C529B29FC00C30073A2FE /* RenameWalletGroupdViewController.swift in Sources */, C08694EA27BD5F7D000A4909 /* CoinGeckoService.swift in Sources */, C04B20A52930E1AF00C008CD /* ActivityService.swift in Sources */, @@ -2021,6 +2036,7 @@ C05B0A332A03FAC9005AA803 /* CollectiblesCollectionHeaderMediumCell.swift in Sources */, C065AF0F2AB09C270061CC64 /* SideMenuBackupViewController.swift in Sources */, C025891D292CD985000D59F6 /* TokenStateService.swift in Sources */, + C0DB69502CF0E122000C1A17 /* TokenDetailsSmallHeadingCell.swift in Sources */, C05848A8280EF5CC007933CA /* ThemePickerViewController.swift in Sources */, C0BF19A32938FB490044D942 /* TokenContractViewController.swift in Sources */, C0CA53AF2C5028DD003AACFF /* SideMenuCell.swift in Sources */, diff --git a/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3d87d818..0a20d85c 100644 --- a/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Kukai Mobile.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -70,7 +70,7 @@ "location" : "https://github.com/kukai-wallet/kukai-core-swift", "state" : { "branch" : "feature/bakers_and_staking", - "revision" : "67a239a5eccfb981cdfe7291ca495b90f4b65256" + "revision" : "67b9f749b1e14d536877e9b6ec806c90fb33c43c" } }, { diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsActivityHeaderCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsActivityHeaderCell.swift index 709471e4..9f213c63 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsActivityHeaderCell.swift +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsActivityHeaderCell.swift @@ -9,4 +9,5 @@ import UIKit class TokenDetailsActivityHeaderCell: UITableViewCell { + } diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.swift new file mode 100644 index 00000000..a02799ee --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.swift @@ -0,0 +1,26 @@ +// +// TokenDetailsPendingUnstakeCell.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 22/11/2024. +// + +import UIKit + +class TokenDetailsPendingUnstakeCell: UITableViewCell { + + @IBOutlet weak var containerView: GradientView! + @IBOutlet weak var amountLabel: UILabel! + @IBOutlet weak var symbolLabel: UILabel! + @IBOutlet weak var fiatLabel: UILabel! + @IBOutlet weak var timeLabel: UILabel! + + public func setup(data: PendingUnstakeData) { + containerView.gradientType = .tableViewCell + + amountLabel.text = data.amount.normalisedRepresentation + symbolLabel.text = "XTZ" + fiatLabel.text = data.fiat + timeLabel.text = data.timeRemaining + } +} diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.xib new file mode 100644 index 00000000..79b61341 --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsPendingUnstakeCell.xib @@ -0,0 +1,113 @@ + + + + + + + + + + + + + Figtree-Bold + + + Figtree-SemiBold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsSmallHeadingCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsSmallHeadingCell.swift new file mode 100644 index 00000000..31c597e3 --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsSmallHeadingCell.swift @@ -0,0 +1,14 @@ +// +// TokenDetailsSmallHeadingCell.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 22/11/2024. +// + +import UIKit + +class TokenDetailsSmallHeadingCell: UITableViewCell { + + @IBOutlet weak var headingLabel: UILabel! + +} diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsSmallHeadingCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsSmallHeadingCell.xib new file mode 100644 index 00000000..fbc40298 --- /dev/null +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsSmallHeadingCell.xib @@ -0,0 +1,54 @@ + + + + + + + + + + + + + Figtree-Bold + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.swift index ef6c1590..c8c60939 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.swift +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.swift @@ -11,12 +11,12 @@ import KukaiCoreSwift class TokenDetailsStakingRewardsCell: UITableViewCell { @IBOutlet weak var containerView: GradientView! - @IBOutlet weak var infoButton: CustomisableButton! @IBOutlet weak var lastBakerIcon: UIImageView! @IBOutlet weak var lastBaker: UILabel! - @IBOutlet weak var lastAmountTitle: UILabel! - @IBOutlet weak var lastAmount: UILabel! + @IBOutlet weak var lastDelegationAmountTitle: UILabel! + @IBOutlet weak var lastDelegationAmount: UILabel! + @IBOutlet weak var lastStakeAmount: UILabel! @IBOutlet weak var lastTimeTitle: UILabel! @IBOutlet weak var lastTime: UILabel! @IBOutlet weak var lastCycleTitle: UILabel! @@ -24,7 +24,8 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { @IBOutlet weak var nextBakerIcon: UIImageView! @IBOutlet weak var nextBaker: UILabel! - @IBOutlet weak var nextAmount: UILabel! + @IBOutlet weak var nextDelegationAmount: UILabel! + @IBOutlet weak var nextStakeAmount: UILabel! @IBOutlet weak var nextTime: UILabel! @IBOutlet weak var nextCycle: UILabel! @@ -41,11 +42,13 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { if let previousReward = data.previousReward { MediaProxyService.load(url: previousReward.bakerLogo, to: lastBakerIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) - let percentage = Decimal(previousReward.fee * 100).rounded(scale: 2, roundingMode: .bankers) + let delegationPercentage = Decimal(previousReward.delegateFee * 100).rounded(scale: 2, roundingMode: .bankers) + let stakingPercentage = Decimal(previousReward.stakeFee * 100).rounded(scale: 2, roundingMode: .bankers) lastBaker.text = previousReward.bakerAlias - lastAmountTitle.text = "Amount (fee)" - lastAmount.text = previousReward.amount.normalisedRepresentation + " (\(percentage)%)" + lastDelegationAmountTitle.text = "Delegation \nAmount (fee)" + lastDelegationAmount.text = previousReward.delegateAmount.normalisedRepresentation + " (\(delegationPercentage)%)" + lastStakeAmount.text = previousReward.stakeAmount.normalisedRepresentation + " (\(stakingPercentage)%)" lastTimeTitle.text = "Time" lastTime.text = previousReward.dateOfPayment.timeAgoDisplay() lastCycleTitle.text = "Cycle" @@ -54,11 +57,13 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { } else if let previousReward = data.estimatedPreviousReward { MediaProxyService.load(url: previousReward.bakerLogo, to: lastBakerIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) - let percentage = Decimal(previousReward.fee * 100).rounded(scale: 2, roundingMode: .bankers) + let delegationPercentage = Decimal(previousReward.delegateFee * 100).rounded(scale: 2, roundingMode: .bankers) + let stakingPercentage = Decimal(previousReward.stakeFee * 100).rounded(scale: 2, roundingMode: .bankers) lastBaker.text = previousReward.bakerAlias - lastAmountTitle.text = "Est Amount (fee)" - lastAmount.text = previousReward.amount.normalisedRepresentation + " (\(percentage)%)" + lastDelegationAmountTitle.text = "Delegation \nEst Amount (fee)" + lastDelegationAmount.text = previousReward.delegateAmount.normalisedRepresentation + " (\(delegationPercentage)%)" + lastStakeAmount.text = previousReward.stakeAmount.normalisedRepresentation + " (\(stakingPercentage)%)" lastTimeTitle.text = "Est Time" lastTime.text = previousReward.dateOfPayment.timeAgoDisplay() lastCycleTitle.text = "Est Cycle" @@ -68,7 +73,7 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { lastBakerIcon.image = UIImage.unknownToken() lastBaker.text = "N/A" - lastAmount.text = "N/A" + lastDelegationAmount.text = "N/A" lastTime.text = "N/A" lastCycle.text = "N/A" } @@ -76,10 +81,12 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { if let nextReward = data.estimatedNextReward { MediaProxyService.load(url: nextReward.bakerLogo, to: nextBakerIcon, withCacheType: .permanent, fallback: UIImage.unknownToken()) - let percentage = Decimal(nextReward.fee * 100).rounded(scale: 2, roundingMode: .bankers) + let delegationPercentage = Decimal(nextReward.delegateFee * 100).rounded(scale: 2, roundingMode: .bankers) + let stakingPercentage = Decimal(nextReward.stakeFee * 100).rounded(scale: 2, roundingMode: .bankers) nextBaker.text = nextReward.bakerAlias - nextAmount.text = nextReward.amount.normalisedRepresentation + " (\(percentage)%)" + nextDelegationAmount.text = nextReward.delegateAmount.normalisedRepresentation + " (\(delegationPercentage)%)" + nextStakeAmount.text = nextReward.stakeAmount.normalisedRepresentation + " (\(stakingPercentage)%)" nextTime.text = nextReward.dateOfPayment.timeAgoDisplay() nextCycle.text = nextReward.cycle == 0 ? "N/A" : nextReward.cycle.description @@ -87,7 +94,7 @@ class TokenDetailsStakingRewardsCell: UITableViewCell { nextBakerIcon.image = UIImage.unknownToken() nextBaker.text = "N/A" - nextAmount.text = "N/A" + nextDelegationAmount.text = "N/A" nextTime.text = "N/A" nextCycle.text = "N/A" } diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.xib b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.xib index 3599d315..61f7c172 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.xib +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsStakingRewardsCell.xib @@ -1,9 +1,9 @@ - + - + @@ -18,46 +18,18 @@ - - + + - + - + - - - + @@ -95,16 +67,37 @@ - + - + + + + + + - + - + - + @@ -186,16 +179,37 @@ - + - + + + + + + - + - + - + @@ -249,19 +263,15 @@ - - - - + - - + @@ -282,32 +292,27 @@ - - - + + + - + + - + - - - - - - diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift index 66c59291..1b5becec 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift @@ -225,13 +225,17 @@ extension TokenDetailsViewController: TokenDetailsBakerDelegate { extension TokenDetailsViewController: TokenDetailsStakeBalanceDelegate { func stakeTapped() { + TransactionService.shared.currentTransactionType = .stake TransactionService.shared.stakeData.chosenBaker = viewModel.baker TransactionService.shared.stakeData.chosenToken = viewModel.token self.performSegue(withIdentifier: "stake-amount", sender: nil) } func unstakeTapped() { - self.alert(errorWithMessage: "Under Construction") + TransactionService.shared.currentTransactionType = .unstake + TransactionService.shared.unstakeData.chosenBaker = viewModel.baker + TransactionService.shared.unstakeData.chosenToken = viewModel.token + self.performSegue(withIdentifier: "stake-amount", sender: nil) } func finalizeTapped() { diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift index a590edbe..df5b7e9e 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift @@ -90,6 +90,17 @@ struct TokenDetailsMessageData: Hashable { let message: String } +struct TokenDetailsSmallSectionHeader: Hashable { + let message: String +} + +struct PendingUnstakeData: Hashable { + let id = UUID() + let amount: XTZAmount + let fiat: String + let timeRemaining: String +} + @objc protocol TokenDetailsViewModelDelegate: AnyObject { @@ -134,6 +145,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { var sendData = TokenDetailsSendData(isBuyTez: false, isDisabled: false) var bakerData: TokenDetailsBakerData? = nil var stakeData: TokenDetailsStakeData? = nil + var pendingUnstakes: [PendingUnstakeData] = [] var onlineDataLoading = LoadingData() var rewardData: AggregateRewardInformation? = nil var activityHeaderData = TokenDetailsActivityHeader(header: true) @@ -158,6 +170,8 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { tableView.register(UINib(nibName: "TokenDetailsActivityHeaderCell_footer", bundle: nil), forCellReuseIdentifier: "TokenDetailsActivityHeaderCell_footer") tableView.register(UINib(nibName: "TokenDetailsLoadingCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsLoadingCell") tableView.register(UINib(nibName: "TokenDetailsMessageCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsMessageCell") + tableView.register(UINib(nibName: "TokenDetailsPendingUnstakeCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsPendingUnstakeCell") + tableView.register(UINib(nibName: "TokenDetailsSmallHeadingCell", bundle: nil), forCellReuseIdentifier: "TokenDetailsSmallHeadingCell") tableView.register(UINib(nibName: "ActivityItemCell", bundle: nil), forCellReuseIdentifier: "ActivityItemCell") @@ -206,6 +220,14 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { cell.delegate = self.delegate return cell + } else if let obj = item.base as? TokenDetailsSmallSectionHeader, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsSmallHeadingCell", for: indexPath) as? TokenDetailsSmallHeadingCell { + cell.headingLabel.text = obj.message + return cell + + } else if let obj = item.base as? PendingUnstakeData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsPendingUnstakeCell", for: indexPath) as? TokenDetailsPendingUnstakeCell { + cell.setup(data: obj) + return cell + } else if let _ = item.base as? LoadingData, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenDetailsLoadingCell", for: indexPath) as? TokenDetailsLoadingCell { cell.setup() return cell @@ -324,11 +346,14 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { self.currentSnapshot.deleteItems([.init(self.onlineDataLoading)]) var newData: [AnyHashableSendable] = [.init(self.bakerData), .init(self.stakeData)] - /* - TODO: pending unstake - */ + + if pendingUnstakes.count > 0 { + newData.append(.init(TokenDetailsSmallSectionHeader(message: "Pending Unstake Requests"))) + newData.append(contentsOf: pendingUnstakes.map({ .init($0) })) + } if let rewardData = rewardData { + newData.append(.init(TokenDetailsSmallSectionHeader(message: "Delegation & Staking Rewards"))) newData.append(.init(rewardData)) } @@ -455,12 +480,33 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { return } - // TODO: add baker voting to query self?.baker = res self?.onlineXTZFetchGroup.leave() } + // Fetch all the pending unstake items + onlineXTZFetchGroup.enter() + DependencyManager.shared.tzktClient.pendingStakingUpdates(forAddress: account.walletAddress, ofType: "unstake") { [weak self] result in + guard let res = try? result.get() else { + // TODO: handle error + self?.onlineXTZFetchGroup.leave() + return + } + + let fiatPerToken = DependencyManager.shared.coinGeckoService.selectedCurrencyRatePerXTZ + self?.pendingUnstakes = res.map { item in + let xtzAmount = item.xtzAmount + let xtzValue = xtzAmount * fiatPerToken + let xtzValueString = DependencyManager.shared.coinGeckoService.format(decimal: xtzValue, numberStyle: .currency, maximumFractionDigits: 2) + + return PendingUnstakeData(amount: item.xtzAmount, fiat: xtzValueString, timeRemaining: item.dateTime.timeAgoDisplay()) + } + + self?.onlineXTZFetchGroup.leave() + } + + // Certain things only work or make sense on mainnet if DependencyManager.shared.currentNetworkType != .ghostnet { @@ -531,17 +577,19 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { let stakeXtzValue = (token.stakedBalance as? XTZAmount ?? .zero()) * fiatPerToken let stakeValue = DependencyManager.shared.coinGeckoService.format(decimal: stakeXtzValue, numberStyle: .currency, maximumFractionDigits: 2) - let unstakeBalance = DependencyManager.shared.coinGeckoService.format(decimal: token.unstakedBalance.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) - let unstakeXtzValue = (token.unstakedBalance as? XTZAmount ?? .zero()) * fiatPerToken - let unstakeValue = DependencyManager.shared.coinGeckoService.format(decimal: unstakeXtzValue, numberStyle: .currency, maximumFractionDigits: 2) + let totalAmountOfPendingUnstake = self?.pendingUnstakes.map({ $0.amount }).reduce(.zero(), +) + let finaliseableAmount = token.unstakedBalance - (totalAmountOfPendingUnstake ?? .zero()) + let finaliseBalance = DependencyManager.shared.coinGeckoService.format(decimal: finaliseableAmount.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) + let finaliseXtzValue = finaliseableAmount * fiatPerToken + let finaliseValue = DependencyManager.shared.coinGeckoService.format(decimal: finaliseXtzValue, numberStyle: .currency, maximumFractionDigits: 2) // We need to prevent users from staking their entire balance so that they have enough balance to unstake // User can also only stake if the baker has enough free space for them let canStake = (account.availableBalance > XTZAmount(fromNormalisedAmount: 1) && enoughSpace) let canUnstake = token.stakedBalance > .zero() - let canFinalize = token.unstakedBalance > .zero() + let canFinalize = finaliseableAmount > .zero() - self?.stakeData = TokenDetailsStakeData(stakedBalance: stakeBalance, stakedValue: stakeValue, finalizeBalance: unstakeBalance, finalizeValue: unstakeValue, canStake: canStake, canUnstake: canUnstake, canFinalize: canFinalize, buttonsDisabled: isWatchWallet) + self?.stakeData = TokenDetailsStakeData(stakedBalance: stakeBalance, stakedValue: stakeValue, finalizeBalance: finaliseBalance, finalizeValue: finaliseValue, canStake: canStake, canUnstake: canUnstake, canFinalize: canFinalize, buttonsDisabled: isWatchWallet) DispatchQueue.main.async { completion() } } diff --git a/Kukai Mobile/Modules/Stake/Stake.storyboard b/Kukai Mobile/Modules/Stake/Stake.storyboard index 61d5850d..6e875162 100644 --- a/Kukai Mobile/Modules/Stake/Stake.storyboard +++ b/Kukai Mobile/Modules/Stake/Stake.storyboard @@ -910,6 +910,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + @@ -1579,7 +1580,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1635,7 +1636,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1654,16 +1655,16 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + - + - + + + @@ -2116,6 +2120,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + @@ -2137,6 +2142,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + diff --git a/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift b/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift index 3c759ceb..740a2a3c 100644 --- a/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift @@ -17,6 +17,7 @@ class StakeAmountViewController: UIViewController { @IBOutlet weak var bakerRewardsValueLabel: UILabel! @IBOutlet weak var tokenNameLabel: UILabel! + @IBOutlet weak var tokenBalanceTitleLabel: UILabel! @IBOutlet weak var tokenBalanceLabel: UILabel! @IBOutlet weak var tokenIcon: UIImageView! @IBOutlet weak var tokenSysmbolLabel: UILabel! @@ -31,6 +32,8 @@ class StakeAmountViewController: UIViewController { private var selectedToken: Token? = nil private var selectedBaker: TzKTBaker? = nil + private var isStake = true + private var maxAmount: TokenAmount? = nil var dimBackground: Bool = true @@ -38,14 +41,19 @@ class StakeAmountViewController: UIViewController { super.viewDidLoad() GradientView.add(toView: self.view, withType: .fullScreenBackground) - selectedToken = TransactionService.shared.stakeData.chosenToken - selectedBaker = TransactionService.shared.stakeData.chosenBaker + isStake = TransactionService.shared.currentTransactionType == .stake + selectedToken = isStake ? TransactionService.shared.stakeData.chosenToken : TransactionService.shared.unstakeData.chosenToken + selectedBaker = isStake ? TransactionService.shared.stakeData.chosenBaker : TransactionService.shared.unstakeData.chosenBaker + guard let token = selectedToken, let baker = selectedBaker else { self.windowError(withTitle: "error".localized(), description: "error-no-token".localized()) self.dismissBottomSheet() return } + self.title = isStake ? "Stake Amount" : "Unstake Amount" + self.tokenBalanceTitleLabel.text = isStake ? "Balance" : "Staked Balance" + // To section MediaProxyService.load(url: baker.logo, to: bakerIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) bakerNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() @@ -55,15 +63,16 @@ class StakeAmountViewController: UIViewController { // Token data - tokenBalanceLabel.text = token.availableBalance.normalisedRepresentation + tokenBalanceLabel.text = isStake ? token.availableBalance.normalisedRepresentation : token.stakedBalance.normalisedRepresentation tokenSysmbolLabel.text = token.symbol fiatLabel?.text = DependencyManager.shared.balanceService.fiatAmountDisplayString(forToken: token, ofAmount: .zero()) tokenIcon.addTokenIcon(token: token) // Textfield + maxAmount = isStake ? (token.availableBalance - TokenAmount(fromNormalisedAmount: 1, decimalPlaces: token.decimalPlaces)) : token.stakedBalance textfield.validatorTextFieldDelegate = self - textfield.validator = TokenAmountValidator(balanceLimit: (token.availableBalance - TokenAmount(fromNormalisedAmount: 1, decimalPlaces: token.decimalPlaces)), decimalPlaces: token.decimalPlaces) + textfield.validator = TokenAmountValidator(balanceLimit: maxAmount ?? .zero(), decimalPlaces: token.decimalPlaces) textfield.addDoneToolbar() textfield.numericAndSeperatorOnly = true @@ -83,7 +92,7 @@ class StakeAmountViewController: UIViewController { } @IBAction func maxTapped(_ sender: Any) { - textfield.text = ((selectedToken?.availableBalance ?? .zero()) - XTZAmount(fromNormalisedAmount: 1)).normalisedRepresentation + textfield.text = maxAmount?.normalisedRepresentation let _ = textfield.revalidateTextfield() } @@ -93,11 +102,11 @@ class StakeAmountViewController: UIViewController { return } - if let token = TransactionService.shared.stakeData.chosenToken, let amount = TokenAmount(fromNormalisedAmount: textfield.text ?? "", decimalPlaces: token.decimalPlaces) { + if let token = selectedToken, let amount = TokenAmount(fromNormalisedAmount: textfield.text ?? "", decimalPlaces: token.decimalPlaces) { self.showLoadingView() - let operations = OperationFactory.stakeOperation(from: selectedWalletMetadata.address, amount: amount) - TransactionService.shared.stakeData.chosenAmount = amount + let operations = isStake ? OperationFactory.stakeOperation(from: selectedWalletMetadata.address, amount: amount) : OperationFactory.unstakeOperation(from: selectedWalletMetadata.address, amount: amount) + if isStake { TransactionService.shared.stakeData.chosenAmount = amount } else { TransactionService.shared.unstakeData.chosenAmount = amount } // Estimate the cost of the operation (ideally display this to a user first and let them confirm) DependencyManager.shared.tezosNodeClient.estimate(operations: operations, walletAddress: selectedWalletMetadata.address, base58EncodedPublicKey: selectedWalletMetadata.bas58EncodedPublicKey) { [weak self] estimationResult in @@ -133,7 +142,7 @@ extension StakeAmountViewController: ValidatorTextFieldDelegate { } func validated(_ validated: Bool, textfield: ValidatorTextField, forText text: String) { - guard let token = TransactionService.shared.sendData.chosenToken else { + guard let token = selectedToken else { return } @@ -161,7 +170,7 @@ extension StakeAmountViewController: ValidatorTextFieldDelegate { } func validateMaxXTZ(input: String) { - if selectedToken?.isXTZ() == true, let balance = selectedToken?.availableBalance, let inputAmount = XTZAmount(fromNormalisedAmount: input, decimalPlaces: 6), balance == inputAmount { + if selectedToken?.isXTZ() == true, let inputAmount = XTZAmount(fromNormalisedAmount: input, decimalPlaces: 6), maxAmount == inputAmount, isStake { warningLabel.isHidden = false } else { warningLabel.isHidden = true diff --git a/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift index 83559c6f..7e811bcb 100644 --- a/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift @@ -14,6 +14,7 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton @IBOutlet var scrollView: UIScrollView! @IBOutlet weak var closeButton: CustomisableButton! + @IBOutlet weak var titleLabel: UILabel! // Connected app @IBOutlet weak var connectedAppLabel: UILabel! @@ -35,6 +36,7 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton @IBOutlet weak var bakerRewardsValueLabel: UILabel! // Stake + @IBOutlet weak var actionTitleLabel: UILabel! @IBOutlet weak var tokenIcon: UIImageView! @IBOutlet weak var tokenAmount: UILabel! @IBOutlet weak var tokenSymbol: UILabel! @@ -50,6 +52,7 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton private var selectedToken: Token? = nil private var selectedBaker: TzKTBaker? = nil + private var isStake = true private var isSendingMaxTez = false var dimBackground: Bool = true @@ -62,14 +65,19 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton testnetWarningView.isHidden = true } - selectedToken = TransactionService.shared.stakeData.chosenToken - selectedBaker = TransactionService.shared.stakeData.chosenBaker + isStake = TransactionService.shared.currentTransactionType == .stake + selectedToken = isStake ? TransactionService.shared.stakeData.chosenToken : TransactionService.shared.unstakeData.chosenToken + selectedBaker = isStake ? TransactionService.shared.stakeData.chosenBaker : TransactionService.shared.unstakeData.chosenBaker + guard let baker = selectedBaker else { self.windowError(withTitle: "error".localized(), description: "error-no-token".localized()) self.dismissBottomSheet() return } + self.titleLabel.text = isStake ? "Confirm Stake" : "Confirm Unstake" + self.actionTitleLabel.text = isStake ? "Stake" : "Unstake" + // Handle wallet connect data if let currentTopic = TransactionService.shared.walletConnectOperationData.request?.topic, let session = WalletKit.instance.getSessions().first(where: { $0.topic == currentTopic }) { @@ -222,6 +230,7 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton func updateFees(isFirstCall: Bool = false) { let feesAndData = isWalletConnectOp ? TransactionService.shared.currentRemoteOperationsAndFeesData : TransactionService.shared.currentOperationsAndFeesData let fee = (feesAndData.fee + feesAndData.maxStorageCost) + let chosenAmount = isStake ? TransactionService.shared.stakeData.chosenAmount : TransactionService.shared.unstakeData.chosenAmount checkForErrorsAndWarnings(errorStackView: slideErrorStackView, errorLabel: errorLabel, totalFee: fee) feeValueLabel.text = fee.normalisedRepresentation + " XTZ" @@ -229,7 +238,7 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton // Sum of send amount + fee is greater than balance, need to adjust send amount // For safety, don't allow this logic coming from WC2, as its likely the user is communicating with a smart contract that likely won't accept recieving less than expected XTZ - if !isWalletConnectOp, let token = selectedToken, token.isXTZ(), let amount = TransactionService.shared.stakeData.chosenAmount, (amount + fee) >= token.availableBalance { + if !isWalletConnectOp, let token = selectedToken, token.isXTZ(), let amount = chosenAmount, (amount + fee) >= token.availableBalance { let oneTez = XTZAmount(fromNormalisedAmount: 1) let updatedValue = ((token.availableBalance - oneTez) - fee) @@ -258,7 +267,7 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton TransactionService.shared.currentOperationsAndFeesData.updateXTZAmount(to: updatedValue) } } else { - updateAmountDisplay(withValue: TransactionService.shared.stakeData.chosenAmount ?? .zero()) + updateAmountDisplay(withValue: chosenAmount ?? .zero()) } } diff --git a/Kukai Mobile/Services/TransactionService.swift b/Kukai Mobile/Services/TransactionService.swift index 48c5526d..176c8506 100644 --- a/Kukai Mobile/Services/TransactionService.swift +++ b/Kukai Mobile/Services/TransactionService.swift @@ -194,6 +194,8 @@ public class TransactionService { public struct UnstakeData { var chosenBaker: TzKTBaker? + var chosenToken: Token? // Incase coming from a dApp for another account + var chosenAmount: TokenAmount? } public struct FinaliseUnstakeData { From 6389d7c064c0d04048e9f906f0d9f372e39313e5 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Mon, 25 Nov 2024 09:24:06 +0000 Subject: [PATCH 15/22] record comment for later --- Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift index df5b7e9e..15628784 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift @@ -337,7 +337,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { } - + // TODO: build finalise UI // At the same time, if we should, load all the other XTZ related content, like baker, staking view, delegation/staking rewards, etc if self.needsToLoadOnlineXTZData { loadOnlineXTZData(token: token) { [weak self] in From 80935499740888fa9e7581a1384f1f3281a9d6c0 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Mon, 25 Nov 2024 14:37:51 +0000 Subject: [PATCH 16/22] - enable delegator rewards on ghostnet - enable finalise unstake - update stake confirm to handle finalise as well as stake and unstake --- .../Modules/Account/Account.storyboard | 17 ++++- .../Account/TokenDetailsViewController.swift | 6 +- .../Account/TokenDetailsViewModel.swift | 75 +++++++++++++------ Kukai Mobile/Modules/Stake/Stake.storyboard | 24 +++--- .../Stake/StakeConfirmViewController.swift | 38 ++++++++-- .../Services/TransactionService.swift | 2 + 6 files changed, 117 insertions(+), 45 deletions(-) diff --git a/Kukai Mobile/Modules/Account/Account.storyboard b/Kukai Mobile/Modules/Account/Account.storyboard index 429c172a..a99408de 100644 --- a/Kukai Mobile/Modules/Account/Account.storyboard +++ b/Kukai Mobile/Modules/Account/Account.storyboard @@ -1,9 +1,9 @@ - + - + @@ -1419,6 +1419,7 @@ + @@ -1851,7 +1852,17 @@ The estimate uses exchange data that might not be up to date and does not take i - + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift index 1b5becec..51738230 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewController.swift @@ -239,6 +239,10 @@ extension TokenDetailsViewController: TokenDetailsStakeBalanceDelegate { } func finalizeTapped() { - self.alert(errorWithMessage: "Under Construction") + self.showLoadingView() + viewModel.createFinaliseOperations { [weak self] errorMessage in + self?.loadingViewHideActivityAndFade() + self?.performSegue(withIdentifier: "confirm-stake", sender: nil) + } } } diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift index 15628784..a6e286a4 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift @@ -152,6 +152,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { var activityFooterData = TokenDetailsActivityHeader(header: false) var activityItems: [TzKTTransactionGroup] = [] var noItemsData = TokenDetailsMessageData(message: "No items avaialble at this time, check again later") + var finaliseableAmount: TokenAmount = .zero() @@ -507,29 +508,29 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { } - // Certain things only work or make sense on mainnet - if DependencyManager.shared.currentNetworkType != .ghostnet { + // Get rewards data from cache or remote + onlineXTZFetchGroup.enter() + if let bakerRewardCache = DiskService.read(type: AggregateRewardInformation.self, fromFileName: TokenDetailsViewModel.bakerRewardsCacheFilename), !bakerRewardCache.isOutOfDate(), !bakerRewardCache.moreThan1CycleBetweenPreiousAndNext() { + self.rewardData = bakerRewardCache + onlineXTZFetchGroup.leave() - // Get rewards data from cache or remote - onlineXTZFetchGroup.enter() - if let bakerRewardCache = DiskService.read(type: AggregateRewardInformation.self, fromFileName: TokenDetailsViewModel.bakerRewardsCacheFilename), !bakerRewardCache.isOutOfDate(), !bakerRewardCache.moreThan1CycleBetweenPreiousAndNext() { - self.rewardData = bakerRewardCache - onlineXTZFetchGroup.leave() - - } else { - DependencyManager.shared.tzktClient.estimateLastAndNextReward(forAddress: account.walletAddress, delegate: delegate) { [weak self] result in - if let res = try? result.get() { - let _ = DiskService.write(encodable: res, toFileName: TokenDetailsViewModel.bakerRewardsCacheFilename) - self?.rewardData = res // TODO: add staking reward values as well - - } else { - Logger.app.error("Error fetching baker data: \(result.getFailure())") - } + } else { + DependencyManager.shared.tzktClient.estimateLastAndNextReward(forAddress: account.walletAddress, delegate: delegate) { [weak self] result in + if let res = try? result.get() { + let _ = DiskService.write(encodable: res, toFileName: TokenDetailsViewModel.bakerRewardsCacheFilename) + self?.rewardData = res - self?.onlineXTZFetchGroup.leave() + } else { + Logger.app.error("Error fetching baker data: \(result.getFailure())") } + + self?.onlineXTZFetchGroup.leave() } - + } + + + // Certain things only work or make sense on mainnet + if DependencyManager.shared.currentNetworkType != .ghostnet { // TODO: cache this and only retrieve no more than once per day // Check voting participation @@ -578,16 +579,17 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { let stakeValue = DependencyManager.shared.coinGeckoService.format(decimal: stakeXtzValue, numberStyle: .currency, maximumFractionDigits: 2) let totalAmountOfPendingUnstake = self?.pendingUnstakes.map({ $0.amount }).reduce(.zero(), +) - let finaliseableAmount = token.unstakedBalance - (totalAmountOfPendingUnstake ?? .zero()) - let finaliseBalance = DependencyManager.shared.coinGeckoService.format(decimal: finaliseableAmount.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) - let finaliseXtzValue = finaliseableAmount * fiatPerToken + let actualFinaliseableAmount = token.unstakedBalance - (totalAmountOfPendingUnstake ?? .zero()) + self?.finaliseableAmount = actualFinaliseableAmount + let finaliseBalance = DependencyManager.shared.coinGeckoService.format(decimal: actualFinaliseableAmount.toNormalisedDecimal() ?? 0, numberStyle: .decimal, maximumFractionDigits: token.decimalPlaces) + let finaliseXtzValue = actualFinaliseableAmount * fiatPerToken let finaliseValue = DependencyManager.shared.coinGeckoService.format(decimal: finaliseXtzValue, numberStyle: .currency, maximumFractionDigits: 2) // We need to prevent users from staking their entire balance so that they have enough balance to unstake // User can also only stake if the baker has enough free space for them let canStake = (account.availableBalance > XTZAmount(fromNormalisedAmount: 1) && enoughSpace) let canUnstake = token.stakedBalance > .zero() - let canFinalize = finaliseableAmount > .zero() + let canFinalize = actualFinaliseableAmount > .zero() self?.stakeData = TokenDetailsStakeData(stakedBalance: stakeBalance, stakedValue: stakeValue, finalizeBalance: finaliseBalance, finalizeValue: finaliseValue, canStake: canStake, canUnstake: canUnstake, canFinalize: canFinalize, buttonsDisabled: isWatchWallet) @@ -735,6 +737,33 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { func isNewBakerFlow() -> Bool { return bakerData?.bakerName == nil } + + func createFinaliseOperations(completion: @escaping ((String?) -> Void)) { + guard let selectedWalletMetadata = DependencyManager.shared.selectedWalletMetadata else { + completion("error-no-destination".localized()) + return + } + + TransactionService.shared.currentTransactionType = .finaliseUnstake + TransactionService.shared.finaliseUnstakeData.chosenAmount = finaliseableAmount + TransactionService.shared.finaliseUnstakeData.chosenBaker = baker + TransactionService.shared.finaliseUnstakeData.chosenToken = token + let operations = OperationFactory.finaliseUnstakeOperation(from: selectedWalletMetadata.address) + + // Estimate the cost of the operation (ideally display this to a user first and let them confirm) + DependencyManager.shared.tezosNodeClient.estimate(operations: operations, walletAddress: selectedWalletMetadata.address, base58EncodedPublicKey: selectedWalletMetadata.bas58EncodedPublicKey) { estimationResult in + + switch estimationResult { + case .success(let estimationResult): + TransactionService.shared.currentOperationsAndFeesData = TransactionService.OperationsAndFeesData(estimatedOperations: estimationResult.operations) + TransactionService.shared.currentForgedString = estimationResult.forgedString + completion(nil) + + case .failure(let estimationError): + completion(estimationError.description) + } + } + } } diff --git a/Kukai Mobile/Modules/Stake/Stake.storyboard b/Kukai Mobile/Modules/Stake/Stake.storyboard index 6e875162..bc874c21 100644 --- a/Kukai Mobile/Modules/Stake/Stake.storyboard +++ b/Kukai Mobile/Modules/Stake/Stake.storyboard @@ -1,9 +1,9 @@ - + - + @@ -1636,7 +1636,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1655,16 +1655,16 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + + + + + + + + + + + + + + + + + + @@ -2183,6 +2316,9 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + diff --git a/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift new file mode 100644 index 00000000..d67278e0 --- /dev/null +++ b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift @@ -0,0 +1,65 @@ +// +// StakeOnboardingContainerViewController.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 27/11/2024. +// + +import UIKit + +class StakeOnboardingContainerViewController: UIViewController { + + @IBOutlet weak var pageIndicator1: PageIndicatorContainerView! + @IBOutlet weak var progressSegment1: UIProgressView! + @IBOutlet weak var pageIndicator2: PageIndicatorContainerView! + @IBOutlet weak var progressSegment2: UIProgressView! + @IBOutlet weak var pageIndicator3: PageIndicatorContainerView! + @IBOutlet weak var progressSegment3: UIProgressView! + @IBOutlet weak var pageIndicator4: PageIndicatorContainerView! + @IBOutlet weak var progressSegment4: UIProgressView! + @IBOutlet weak var pageIndicator5: PageIndicatorContainerView! + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.pageIndicator1.setInprogress(pageNumber: 1) + + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in + self?.pageIndicator1.setComplete() + self?.setProgressSegmentComplete(self?.progressSegment1) + self?.pageIndicator2.setInprogress(pageNumber: 2) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 6) { [weak self] in + self?.pageIndicator2.setComplete() + self?.setProgressSegmentComplete(self?.progressSegment2) + self?.pageIndicator3.setInprogress(pageNumber: 3) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 9) { [weak self] in + self?.pageIndicator3.setComplete() + self?.setProgressSegmentComplete(self?.progressSegment3) + self?.pageIndicator4.setInprogress(pageNumber: 4) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 12) { [weak self] in + self?.pageIndicator4.setComplete() + self?.setProgressSegmentComplete(self?.progressSegment4) + self?.pageIndicator5.setInprogress(pageNumber: 5) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in + self?.pageIndicator5.setComplete() + } + } + + func setProgressSegmentComplete(_ view: UIProgressView?) { + UIView.animate(withDuration: 0.7) { + view?.setProgress(1, animated: true) + } + } +} From 2b62c427a361736dd732d0102a60aa1fbdb78a39 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Fri, 29 Nov 2024 15:13:17 +0000 Subject: [PATCH 21/22] - add new stake onboarding container controller - move learn more items into stake storyboard - tweak onboarding page controller to allow storyboard to pass values in - wire up start instructions, first info panel, and choose baker flow - temporarily disable sending transaction for testing purposes --- Kukai Mobile.xcodeproj/project.pbxproj | 8 + .../Controls/BottomSheetLargeSegue.swift | 2 +- .../Controls/BottomSheetVariableSegue.swift | 2 +- .../OnboardingPageViewController.swift | 29 +- .../Controls/PageIndicatorContainerView.swift | 11 + Kukai Mobile/Controls/SlideSegue.swift | 21 + .../Modules/Account/Account.storyboard | 599 ++------------ .../Modules/Account/AccountViewModel.swift | 6 +- .../TokenDetailsLearnMoreViewModel.swift | 22 +- .../Account/TokenDetailsViewModel.swift | 2 +- .../Modules/Fees/FeeInfoViewController.swift | 10 +- .../Modules/Login/LoginViewController.swift | 5 +- .../SendAbstractConfirmViewController.swift | 21 +- .../ChooseBakerConfirmViewController.swift | 5 +- .../Stake/ChooseBakerViewController.swift | 7 +- Kukai Mobile/Modules/Stake/Stake.storyboard | 757 ++++++++++++++++++ ...takeOnboardingContainerNavController.swift | 17 + ...akeOnboardingContainerViewController.swift | 82 +- 18 files changed, 1045 insertions(+), 561 deletions(-) create mode 100644 Kukai Mobile/Controls/SlideSegue.swift create mode 100644 Kukai Mobile/Modules/Stake/StakeOnboardingContainerNavController.swift diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index f09415f3..4b58173f 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -89,6 +89,7 @@ C025891F292D1154000D59F6 /* HiddenTokenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C025891E292D1154000D59F6 /* HiddenTokenCell.swift */; }; C0258921292D1166000D59F6 /* FavouriteTokenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0258920292D1166000D59F6 /* FavouriteTokenCell.swift */; }; C026747B2AB1D17100B3538E /* Test_09_SideMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C026747A2AB1D17100B3538E /* Test_09_SideMenu.swift */; }; + C02810422CFA0D9E005EA5E8 /* StakeOnboardingContainerNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02810412CFA0D9E005EA5E8 /* StakeOnboardingContainerNavController.swift */; }; C02914362A6589D900A8AF08 /* CreatePasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02914352A6589D900A8AF08 /* CreatePasscodeViewController.swift */; }; C02914382A6589EE00A8AF08 /* ConfirmPasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02914372A6589EE00A8AF08 /* ConfirmPasscodeViewController.swift */; }; C02A4FDA27DA421100F42DE4 /* Date+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02A4FD927DA421100F42DE4 /* Date+extensions.swift */; }; @@ -329,6 +330,7 @@ C0C6B5CD28CA25BD00368AEA /* PublicBakerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5CC28CA25BD00368AEA /* PublicBakerCell.swift */; }; C0C6B5CF28CA27AF00368AEA /* ChooseBakerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5CE28CA27AF00368AEA /* ChooseBakerViewController.swift */; }; C0C6B5D128CA2A6B00368AEA /* ChooseBakerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6B5D028CA2A6B00368AEA /* ChooseBakerViewModel.swift */; }; + C0C6D9B32CF8C6F100ABB0A7 /* SlideSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C6D9B22CF8C6F100ABB0A7 /* SlideSegue.swift */; }; C0C7A0FE2955B8CF00ADCA51 /* NoContactsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C7A0FD2955B8CF00ADCA51 /* NoContactsCell.swift */; }; C0C7DFB829BF34ED00F60E0C /* SideMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0C7DFB729BF34ED00F60E0C /* SideMenu.storyboard */; }; C0C7DFBA29BF37FF00F60E0C /* SideMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C7DFB929BF37FF00F60E0C /* SideMenuViewController.swift */; }; @@ -528,6 +530,7 @@ C025891E292D1154000D59F6 /* HiddenTokenCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenTokenCell.swift; sourceTree = ""; }; C0258920292D1166000D59F6 /* FavouriteTokenCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavouriteTokenCell.swift; sourceTree = ""; }; C026747A2AB1D17100B3538E /* Test_09_SideMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test_09_SideMenu.swift; sourceTree = ""; }; + C02810412CFA0D9E005EA5E8 /* StakeOnboardingContainerNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeOnboardingContainerNavController.swift; sourceTree = ""; }; C02914352A6589D900A8AF08 /* CreatePasscodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePasscodeViewController.swift; sourceTree = ""; }; C02914372A6589EE00A8AF08 /* ConfirmPasscodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmPasscodeViewController.swift; sourceTree = ""; }; C02A4FD927DA421100F42DE4 /* Date+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+extensions.swift"; sourceTree = ""; }; @@ -776,6 +779,7 @@ C0C6B5CC28CA25BD00368AEA /* PublicBakerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicBakerCell.swift; sourceTree = ""; }; C0C6B5CE28CA27AF00368AEA /* ChooseBakerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseBakerViewController.swift; sourceTree = ""; }; C0C6B5D028CA2A6B00368AEA /* ChooseBakerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseBakerViewModel.swift; sourceTree = ""; }; + C0C6D9B22CF8C6F100ABB0A7 /* SlideSegue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideSegue.swift; sourceTree = ""; }; C0C7A0FD2955B8CF00ADCA51 /* NoContactsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoContactsCell.swift; sourceTree = ""; }; C0C7DFB729BF34ED00F60E0C /* SideMenu.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SideMenu.storyboard; sourceTree = ""; }; C0C7DFB929BF37FF00F60E0C /* SideMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuViewController.swift; sourceTree = ""; }; @@ -1236,6 +1240,7 @@ C057E3872BE2D03700189234 /* CustomNavigationController.swift */, C0CA53AC2C4FE123003AACFF /* GradientView.swift */, C04BA1F72CF766DA00951249 /* PageIndicatorContainerView.swift */, + C0C6D9B22CF8C6F100ABB0A7 /* SlideSegue.swift */, ); path = Controls; sourceTree = ""; @@ -1635,6 +1640,7 @@ C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */, C0DB694C2CEF567A000C1A17 /* StakeConfirmViewController.swift */, C04BA1F52CF765B900951249 /* StakeOnboardingContainerViewController.swift */, + C02810412CFA0D9E005EA5E8 /* StakeOnboardingContainerNavController.swift */, ); path = Stake; sourceTree = ""; @@ -2044,6 +2050,7 @@ C01AE5DC28A26C44008F93E8 /* AddLiquidityConfirmViewController.swift in Sources */, C0B8C8A129DC2AD200C8AFD5 /* FaceIdViewController.swift in Sources */, C073C0DE29E6CADC0064FBEF /* LoadingContainerCell.swift in Sources */, + C02810422CFA0D9E005EA5E8 /* StakeOnboardingContainerNavController.swift in Sources */, C0411B4729DEBFAD00778E1E /* TextFieldSuggestionAccessoryView.swift in Sources */, C0B03643269EE2070071ACD0 /* AppDelegate.swift in Sources */, C022DA462B69433400F61CD1 /* SendBatchDetailsViewController.swift in Sources */, @@ -2101,6 +2108,7 @@ C0D6D50628B66B07001B8130 /* EditFeesViewController.swift in Sources */, C08C973F2908192A00249959 /* UICollectionViewCell+extensions.swift in Sources */, C04BA1EE2CF4CFAF00951249 /* LearnMoreSectionHeaderCell.swift in Sources */, + C0C6D9B32CF8C6F100ABB0A7 /* SlideSegue.swift in Sources */, C07DE53A28BCFD2800C32D42 /* AddLiquidityViewModel.swift in Sources */, C02D7CAE27BEB240006A8E39 /* CurrencyViewController.swift in Sources */, C04BA1F82CF766EA00951249 /* PageIndicatorContainerView.swift in Sources */, diff --git a/Kukai Mobile/Controls/BottomSheetLargeSegue.swift b/Kukai Mobile/Controls/BottomSheetLargeSegue.swift index 2e26d54f..3a299857 100644 --- a/Kukai Mobile/Controls/BottomSheetLargeSegue.swift +++ b/Kukai Mobile/Controls/BottomSheetLargeSegue.swift @@ -16,7 +16,7 @@ public class BottomSheetLargeSegue: UIStoryboardSegue { let customId = UISheetPresentationController.Detent.Identifier("large-minus-background-effect") let customDetent = UISheetPresentationController.Detent.custom(identifier: customId) { context in - return context.maximumDetentValue - 0.1 + return context.maximumDetentValue - 1 } dest.detents = [customDetent] dest.prefersGrabberVisible = true diff --git a/Kukai Mobile/Controls/BottomSheetVariableSegue.swift b/Kukai Mobile/Controls/BottomSheetVariableSegue.swift index f9debf41..5e90261c 100644 --- a/Kukai Mobile/Controls/BottomSheetVariableSegue.swift +++ b/Kukai Mobile/Controls/BottomSheetVariableSegue.swift @@ -48,7 +48,7 @@ public class BottomSheetCustomSegue: UIStoryboardSegue { } else { let customId = UISheetPresentationController.Detent.Identifier("large-minus-background-effect") let customDetent = UISheetPresentationController.Detent.custom(identifier: customId) { context in - return context.maximumDetentValue - 0.1 + return context.maximumDetentValue - 1 } dest.detents = [customDetent] } diff --git a/Kukai Mobile/Controls/OnboardingPageViewController.swift b/Kukai Mobile/Controls/OnboardingPageViewController.swift index 44cd1c95..bcf03d7f 100644 --- a/Kukai Mobile/Controls/OnboardingPageViewController.swift +++ b/Kukai Mobile/Controls/OnboardingPageViewController.swift @@ -14,11 +14,12 @@ protocol OnboardingPageViewControllerDelegate: AnyObject { /// A wrapper around the UIPageViewController that takes in a collection of viewControllers via an `IBInspectable`, and implements all the standard scroll logic and UIPageControl /// to create an onboarding / intro / explanitory section in the app, to visually explain a topic or collection of topics to the user. -class OnboardingPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate { +class OnboardingPageViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate { + public var pageController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal) public var items: [UIViewController] = [] public var startIndex: Int = 0 - private var pageControl: UIPageControl? = nil + public var pageControl: UIPageControl? = nil /** comma separated string containing any number of UIViewController storyboard ids, used to init UIViewControllers to act as pages in the page view controller @@ -30,20 +31,29 @@ class OnboardingPageViewController: UIPageViewController, UIPageViewControllerDa public weak var pageDelegate: OnboardingPageViewControllerDelegate? = nil required init?(coder: NSCoder) { - super.init(transitionStyle: .scroll, navigationOrientation: .horizontal) + super.init(coder: coder) } override func viewDidLoad() { super.viewDidLoad() + self.addChild(pageController) + self.view.addSubview(pageController.view) + NSLayoutConstraint.activate([ + pageController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + pageController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), + pageController.view.topAnchor.constraint(equalTo: self.view.topAnchor), + pageController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor) + ]) + let ids = commaSeperatedStoryboardIds.components(separatedBy: ",") for id in ids { items.append(self.storyboard?.instantiateViewController(identifier: id) ?? UIViewController()) } - setViewControllers([items[0]], direction: .forward, animated: false, completion: nil) - self.dataSource = self - self.delegate = self + pageController.setViewControllers([items[0]], direction: .forward, animated: false, completion: nil) + pageController.dataSource = self + pageController.delegate = self pageControl = UIPageControl() pageControl?.translatesAutoresizingMaskIntoConstraints = false @@ -63,7 +73,7 @@ class OnboardingPageViewController: UIPageViewController, UIPageViewControllerDa } - self.setViewControllers([items[startIndex]], direction: .forward, animated: false, completion: nil) + pageController.setViewControllers([items[startIndex]], direction: .forward, animated: false, completion: nil) self.pageControl?.currentPage = startIndex if !showPageControl { @@ -117,12 +127,13 @@ class OnboardingPageViewController: UIPageViewController, UIPageViewControllerDa } public func scrollTo(index: Int) { - guard let currentVc = self.viewControllers?.first, let viewControllerIndex = items.firstIndex(of: currentVc) else { + guard let currentVc = pageController.viewControllers?.first, let viewControllerIndex = items.firstIndex(of: currentVc) else { return } let vc = items[index] let isForward = viewControllerIndex < index - self.setViewControllers([vc], direction: isForward ? .forward : .reverse, animated: true, completion: nil) + pageController.setViewControllers([vc], direction: isForward ? .forward : .reverse, animated: true, completion: nil) + self.pageControl?.currentPage = index } } diff --git a/Kukai Mobile/Controls/PageIndicatorContainerView.swift b/Kukai Mobile/Controls/PageIndicatorContainerView.swift index bad8e58b..bd37eea4 100644 --- a/Kukai Mobile/Controls/PageIndicatorContainerView.swift +++ b/Kukai Mobile/Controls/PageIndicatorContainerView.swift @@ -97,6 +97,11 @@ public class PageIndicatorContainerView: UIView { checkImageView.alpha = 0 } + /** + - Animate the size of pagePendingView to the size of pageInprogressView + - After a brief delay to allow effect to show, start hiding pagePendingView and start showing pageInprogressView + - Setup the next stage by transforming checkImageView to a fraction of its current size + */ public func setInprogress(pageNumber: Int) { pageNumberLabel.text = pageNumber.description UIView.animate(withDuration: 0.3) { [weak self] in @@ -105,6 +110,7 @@ public class PageIndicatorContainerView: UIView { } completion: { [weak self] _ in self?.pageCompleteView.alpha = 0 self?.checkImageView.alpha = 0 + self?.checkImageView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) } UIView.animate(withDuration: 0.3, delay: 0.2) { [weak self] in @@ -115,6 +121,10 @@ public class PageIndicatorContainerView: UIView { } } + /** + - Animate hiding pageNumberLabel, and animate showing pageCompleteView + - Afterwards transform checkImageView back to its size, to give the appearance of it popping into the control + */ public func setComplete() { UIView.animate(withDuration: 0.3) { [weak self] in self?.pageNumberLabel.alpha = 0 @@ -123,6 +133,7 @@ public class PageIndicatorContainerView: UIView { } completion: { _ in UIView.animate(withDuration: 0.3) { [weak self] in self?.checkImageView.alpha = 1 + self?.checkImageView.transform = CGAffineTransform(scaleX: 1, y: 1) } } diff --git a/Kukai Mobile/Controls/SlideSegue.swift b/Kukai Mobile/Controls/SlideSegue.swift new file mode 100644 index 00000000..9eaeb8b4 --- /dev/null +++ b/Kukai Mobile/Controls/SlideSegue.swift @@ -0,0 +1,21 @@ +// +// SlideSegue.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 28/11/2024. +// + +import UIKit + +class SlideSegue: UIStoryboardSegue { + + override func perform() { + let transition: CATransition = CATransition() + transition.duration = 0.3 + transition.type = CATransitionType.push + transition.subtype = UIApplication.shared.userInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.rightToLeft ? .fromLeft : .fromRight + + source.navigationController?.view.layer.add(transition, forKey: nil) + source.navigationController?.pushViewController(destination, animated: false) + } +} diff --git a/Kukai Mobile/Modules/Account/Account.storyboard b/Kukai Mobile/Modules/Account/Account.storyboard index 5639593f..5780ce26 100644 --- a/Kukai Mobile/Modules/Account/Account.storyboard +++ b/Kukai Mobile/Modules/Account/Account.storyboard @@ -758,23 +758,23 @@ - + - @@ -413,6 +439,8 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + @@ -422,12 +450,45 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2302,10 +2363,703 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2349,6 +3103,9 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + diff --git a/Kukai Mobile/Modules/Stake/StakeOnboardingContainerNavController.swift b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerNavController.swift new file mode 100644 index 00000000..c0d34f24 --- /dev/null +++ b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerNavController.swift @@ -0,0 +1,17 @@ +// +// StakeOnboardingContainerNavController.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 29/11/2024. +// + +import UIKit + +class StakeOnboardingContainerNavController: UINavigationController, BottomSheetCustomCalculateProtocol { + + var dimBackground = true + + func bottomSheetHeight() -> CGFloat { + return view.bounds.height * 0.75 + } +} diff --git a/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift index d67278e0..b20ee93f 100644 --- a/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift @@ -18,16 +18,26 @@ class StakeOnboardingContainerViewController: UIViewController { @IBOutlet weak var pageIndicator4: PageIndicatorContainerView! @IBOutlet weak var progressSegment4: UIProgressView! @IBOutlet weak var pageIndicator5: PageIndicatorContainerView! + @IBOutlet weak var actionButton: CustomisableButton! + + @IBOutlet weak var navigationContainerView: UIView! + private var childNavigationController: UINavigationController? = nil + private var currentChildViewController: UIViewController? = nil override func viewDidLoad() { super.viewDidLoad() + GradientView.add(toView: self.view, withType: .fullScreenBackground) + + actionButton.customButtonType = .primary + self.pageIndicator1.setInprogress(pageNumber: 1) + + //NotificationCenter.default.addObserver(self, selector: #selector(bakerConfirmation), name: ChooseBakerViewController.notificationNameBakerChosen, object: nil) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.pageIndicator1.setInprogress(pageNumber: 1) - + /* DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in self?.pageIndicator1.setComplete() self?.setProgressSegmentComplete(self?.progressSegment1) @@ -55,6 +65,13 @@ class StakeOnboardingContainerViewController: UIViewController { DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in self?.pageIndicator5.setComplete() } + */ + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + //NotificationCenter.default.removeObserver(self, name: ChooseBakerViewController.notificationNameBakerChosen, object: nil) } func setProgressSegmentComplete(_ view: UIProgressView?) { @@ -62,4 +79,65 @@ class StakeOnboardingContainerViewController: UIViewController { view?.setProgress(1, animated: true) } } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "embed", let dest = segue.destination as? UINavigationController { + childNavigationController = dest + + } + } + + @IBAction func actionButtonTapped(_ sender: Any) { + guard let childNav = childNavigationController, let currentChildVc = childNav.viewControllers.last else { + self.windowError(withTitle: "error".localized(), description: "Unknown error") + return + } + + currentChildViewController = currentChildVc + switch currentChildVc.title { + case "step1": + currentChildVc.performSegue(withIdentifier: "next", sender: nil) + self.pageIndicator1.setComplete() + self.setProgressSegmentComplete(self.progressSegment1) + self.pageIndicator2.setInprogress(pageNumber: 2) + + case "step2": + if handlePageControllerNext(vc: currentChildVc) == true { + actionButton.setTitle("Choose Baker", for: .normal) + self.pageIndicator2.setComplete() + self.setProgressSegmentComplete(self.progressSegment2) + self.pageIndicator3.setInprogress(pageNumber: 3) + } + + case "step3": + self.performSegue(withIdentifier: "chooseBaker", sender: nil) + + default: + self.windowError(withTitle: "error".localized(), description: "Unknown error") + } + } + + private func handlePageControllerNext(vc: UIViewController) -> Bool? { + guard let pageController = (vc as? OnboardingPageViewController), let pageControl = pageController.pageControl else { + self.windowError(withTitle: "error".localized(), description: "Unknown error") + return nil + } + + if pageControl.currentPage == pageControl.numberOfPages-1 { + vc.performSegue(withIdentifier: "next", sender: nil) + return true + } else { + pageController.scrollTo(index: (pageController.pageControl?.currentPage ?? 0) + 1) + return false + } + } + + /* + @objc private func bakerConfirmation() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in + self?.hideLoadingView() + self?.performSegue(withIdentifier: "confirmBaker", sender: nil) + } + } + */ } From d34b97692f1a3fd6cf34f203dff2001880b40977 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Mon, 2 Dec 2024 15:41:31 +0000 Subject: [PATCH 22/22] - wire up real code for delegation/staking onboarding - bug fix some loading - remove unnecessary extra loading - remove all hacks to speed up testing --- .../EnterAddressComponent.swift | 11 +- .../UIViewController+extensions.swift | 2 +- .../Account/TokenDetailsViewModel.swift | 18 ++ .../Modules/Login/LoginViewController.swift | 5 +- .../SendAbstractConfirmViewController.swift | 1 + .../ChooseBakerConfirmViewController.swift | 5 +- .../Stake/ChooseBakerViewController.swift | 21 +- .../Modules/Stake/ChooseBakerViewModel.swift | 9 - .../EnterCustomBakerViewController.swift | 1 + Kukai Mobile/Modules/Stake/Stake.storyboard | 237 +++++++++++++++++- ...akeOnboardingContainerViewController.swift | 127 ++++++---- 11 files changed, 347 insertions(+), 90 deletions(-) diff --git a/Kukai Mobile/Controls/EnterAddressComponent/EnterAddressComponent.swift b/Kukai Mobile/Controls/EnterAddressComponent/EnterAddressComponent.swift index da908ed4..63b49010 100644 --- a/Kukai Mobile/Controls/EnterAddressComponent/EnterAddressComponent.swift +++ b/Kukai Mobile/Controls/EnterAddressComponent/EnterAddressComponent.swift @@ -42,6 +42,13 @@ public class EnterAddressComponent: UIView { private var currentSelectedType: AddressType = .tezosAddress public weak var delegate: EnterAddressComponentDelegate? = nil + public var allowEmpty: Bool = false { + didSet { + if (self.textField.text == nil || self.textField.text == "") && allowEmpty { + self.sendButton.isEnabled = true + } + } + } required init?(coder aDecoder: NSCoder) { @@ -71,7 +78,7 @@ public class EnterAddressComponent: UIView { textField.validatorTextFieldDelegate = self self.hideError(animate: false) - self.sendButton.isEnabled = false + self.sendButton.isEnabled = allowEmpty } public func updateAvilableAddressTypes(_ types: [AddressType]) { @@ -275,7 +282,7 @@ extension EnterAddressComponent: ValidatorTextFieldDelegate { self.sendButton.isEnabled = true } else if text == "" { - self.sendButton.isEnabled = false + self.sendButton.isEnabled = allowEmpty } else { self.sendButton.isEnabled = false diff --git a/Kukai Mobile/Extensions/UIViewController+extensions.swift b/Kukai Mobile/Extensions/UIViewController+extensions.swift index a73dacb2..5db27bba 100644 --- a/Kukai Mobile/Extensions/UIViewController+extensions.swift +++ b/Kukai Mobile/Extensions/UIViewController+extensions.swift @@ -42,7 +42,7 @@ extension UIViewController { activityViewStatusLabel.font = UIFont.custom(ofType: .medium, andSize: 16) activityViewStatusLabel.textColor = UIColor.white activityViewStatusLabel.textAlignment = .center - activityViewStatusLabel.frame = CGRect(x: view.center.x, y: view.center.y, width: view.frame.width - 64, height: 50) + activityViewStatusLabel.frame = CGRect(x: view.center.x, y: view.center.y, width: view.frame.width - 64, height: 200) UIViewController.activityViewActivityIndicator.color = UIColor.white diff --git a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift index 12c418ff..95cc5dfe 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsViewModel.swift @@ -7,6 +7,7 @@ import UIKit import KukaiCoreSwift +import Combine import OSLog struct AllChartData: Hashable { @@ -122,6 +123,7 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { private let chartDateFormatter = DateFormatter(withFormat: "MMM dd HH:mm a") private var initialChartLoad = true private var onlineXTZFetchGroup = DispatchGroup() + private var bag = [AnyCancellable]() // Set by VC weak var delegate: (TokenDetailsViewModelDelegate & TokenDetailsBakerDelegate & TokenDetailsStakeBalanceDelegate)? = nil @@ -155,6 +157,22 @@ public class TokenDetailsViewModel: ViewModel, TokenDetailsChartCellDelegate { var finaliseableAmount: TokenAmount = .zero() + override init() { + super.init() + + DependencyManager.shared.$addressRefreshed + .dropFirst() + .sink { [weak self] address in + let selectedAddress = DependencyManager.shared.selectedWalletAddress ?? "" + if self?.dataSource != nil && selectedAddress == address { + self?.refresh(animate: true) + } + }.store(in: &bag) + } + + deinit { + bag.forEach({ $0.cancel() }) + } diff --git a/Kukai Mobile/Modules/Login/LoginViewController.swift b/Kukai Mobile/Modules/Login/LoginViewController.swift index f50ed1a6..75bef38c 100644 --- a/Kukai Mobile/Modules/Login/LoginViewController.swift +++ b/Kukai Mobile/Modules/Login/LoginViewController.swift @@ -63,7 +63,7 @@ class LoginViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - /* // TODO: uncomment + // Hide biometric button if its not enabled, or we are in the middle of the edit passcode flow if isEditPasscodeMode() || CurrentDevice.biometricTypeAuthorized() == .none || StorageService.isBiometricEnabled() == false { useBiometricsButton.isHidden = true @@ -83,9 +83,6 @@ class LoginViewController: UIViewController { // Edit passcode popup self.hiddenTextfield.becomeFirstResponder() } - */ - - self.next() } override func viewDidDisappear(_ animated: Bool) { diff --git a/Kukai Mobile/Modules/Send/SendAbstractConfirmViewController.swift b/Kukai Mobile/Modules/Send/SendAbstractConfirmViewController.swift index f00ae6a6..84919a1a 100644 --- a/Kukai Mobile/Modules/Send/SendAbstractConfirmViewController.swift +++ b/Kukai Mobile/Modules/Send/SendAbstractConfirmViewController.swift @@ -114,6 +114,7 @@ class SendAbstractConfirmViewController: UIViewController { if collapseOnly == false { if isDuringStakeOnboardingFlow { topMostNavigationController?.dismiss(animated: true) + } else { (self?.presentingViewController as? UINavigationController)?.popToHome() } diff --git a/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift b/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift index 392a6a6a..d11a502b 100644 --- a/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift +++ b/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift @@ -181,10 +181,7 @@ class ChooseBakerConfirmViewController: SendAbstractConfirmViewController, Slide func didCompleteSlide() { self.blockInteraction(exceptFor: [closeButton]) - - // TODO: swap back - //self.performAuth() - self.handleApproval(opHash: "", slideButton: self.slideButton) + self.performAuth() } override func authSuccessful() { diff --git a/Kukai Mobile/Modules/Stake/ChooseBakerViewController.swift b/Kukai Mobile/Modules/Stake/ChooseBakerViewController.swift index 18eb48c1..a667d15a 100644 --- a/Kukai Mobile/Modules/Stake/ChooseBakerViewController.swift +++ b/Kukai Mobile/Modules/Stake/ChooseBakerViewController.swift @@ -13,7 +13,7 @@ class ChooseBakerViewController: UIViewController { @IBOutlet weak var tableView: UITableView! - private let viewModel = ChooseBakerViewModel() + private var viewModel = ChooseBakerViewModel() private var cancellable: AnyCancellable? private let footerView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 2)) private let blankView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0.1)) @@ -49,7 +49,10 @@ class ChooseBakerViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - viewModel.refresh(animate: false) + + if viewModel.bakers.count == 0 { + viewModel.refresh(animate: false) + } } public func enteredCustomBaker(address: String) { @@ -58,9 +61,10 @@ class ChooseBakerViewController: UIViewController { if address == "" { let currentDelegate = DependencyManager.shared.balanceService.account.delegate let name = currentDelegate?.alias ?? currentDelegate?.address.truncateTezosAddress() ?? "" - let baker = TzKTBaker(address: "", name: name) + let baker = TzKTBaker(address: currentDelegate?.address ?? "", name: name) TransactionService.shared.delegateData.chosenBaker = baker + TransactionService.shared.stakeData.chosenBaker = baker TransactionService.shared.delegateData.isAdd = false } else { @@ -68,11 +72,13 @@ class ChooseBakerViewController: UIViewController { if let foundBaker = viewModel.bakerFor(address: address) { TransactionService.shared.delegateData.chosenBaker = foundBaker + TransactionService.shared.stakeData.chosenBaker = foundBaker TransactionService.shared.delegateData.isAdd = true } else { let baker = TzKTBaker(address: address, name: address.truncateTezosAddress()) TransactionService.shared.delegateData.chosenBaker = baker + TransactionService.shared.stakeData.chosenBaker = baker TransactionService.shared.delegateData.isAdd = true } } @@ -97,7 +103,13 @@ class ChooseBakerViewController: UIViewController { return } - let operations = OperationFactory.delegateOperation(to: toAddress, from: selectedWallet.address) + var operations: [KukaiCoreSwift.Operation] = [] + if TransactionService.shared.delegateData.isAdd == true { + operations = OperationFactory.delegateOperation(to: toAddress, from: selectedWallet.address) + } else { + operations = OperationFactory.undelegateOperation(address: selectedWallet.address) + } + DependencyManager.shared.tezosNodeClient.estimate(operations: operations, walletAddress: selectedWallet.address, base58EncodedPublicKey: selectedWallet.publicKeyBase58encoded()) { [weak self] estimationResult in self?.hideLoadingView() @@ -132,6 +144,7 @@ extension ChooseBakerViewController: UITableViewDelegate { if let baker = viewModel.bakerFor(indexPath: indexPath) { TransactionService.shared.delegateData.chosenBaker = baker + TransactionService.shared.stakeData.chosenBaker = baker TransactionService.shared.delegateData.isAdd = true self.performSegue(withIdentifier: "details", sender: nil) diff --git a/Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift b/Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift index 42a82d50..24e5d8a1 100644 --- a/Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift +++ b/Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift @@ -32,15 +32,6 @@ class ChooseBakerViewModel: ViewModel, UITableViewDiffableDataSourceHandler { override init() { super.init() - - DependencyManager.shared.$addressRefreshed - .dropFirst() - .sink { [weak self] address in - let selectedAddress = DependencyManager.shared.selectedWalletAddress ?? "" - if self?.dataSource != nil && selectedAddress == address { - self?.refresh(animate: true) - } - }.store(in: &bag) } deinit { diff --git a/Kukai Mobile/Modules/Stake/EnterCustomBakerViewController.swift b/Kukai Mobile/Modules/Stake/EnterCustomBakerViewController.swift index 4b4122bf..ada6d567 100644 --- a/Kukai Mobile/Modules/Stake/EnterCustomBakerViewController.swift +++ b/Kukai Mobile/Modules/Stake/EnterCustomBakerViewController.swift @@ -18,6 +18,7 @@ class EnterCustomBakerViewController: UIViewController, EnterAddressComponentDel enterAddressComponent.headerLabel.text = "Baker:" enterAddressComponent.updateAvilableAddressTypes([.tezosAddress, .tezosDomain]) enterAddressComponent.delegate = self + enterAddressComponent.allowEmpty = true // undelegate by entering blank into field } func validatedInput(entered: String, validAddress: Bool, ofType: AddressType) { diff --git a/Kukai Mobile/Modules/Stake/Stake.storyboard b/Kukai Mobile/Modules/Stake/Stake.storyboard index 00971a25..fdc72c24 100644 --- a/Kukai Mobile/Modules/Stake/Stake.storyboard +++ b/Kukai Mobile/Modules/Stake/Stake.storyboard @@ -253,7 +253,7 @@ - + @@ -293,7 +293,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -434,6 +434,9 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + + + @@ -451,6 +454,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + @@ -471,7 +475,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -704,7 +708,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -745,7 +749,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1090,6 +1094,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y + @@ -1114,7 +1119,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1703,7 +1708,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1711,7 +1716,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1719,7 +1724,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -2345,7 +2350,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -2353,7 +2358,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -2361,7 +2366,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -3050,11 +3055,219 @@ Bakers also vote on behalf of users to make changes to the network, during the G + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift index b20ee93f..cbd8a458 100644 --- a/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift @@ -6,6 +6,8 @@ // import UIKit +import KukaiCoreSwift +import Combine class StakeOnboardingContainerViewController: UIViewController { @@ -23,55 +25,41 @@ class StakeOnboardingContainerViewController: UIViewController { @IBOutlet weak var navigationContainerView: UIView! private var childNavigationController: UINavigationController? = nil private var currentChildViewController: UIViewController? = nil + private var bag = [AnyCancellable]() + private var currentStep: String = "" override func viewDidLoad() { super.viewDidLoad() GradientView.add(toView: self.view, withType: .fullScreenBackground) actionButton.customButtonType = .primary - self.pageIndicator1.setInprogress(pageNumber: 1) - //NotificationCenter.default.addObserver(self, selector: #selector(bakerConfirmation), name: ChooseBakerViewController.notificationNameBakerChosen, object: nil) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - /* - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in - self?.pageIndicator1.setComplete() - self?.setProgressSegmentComplete(self?.progressSegment1) - self?.pageIndicator2.setInprogress(pageNumber: 2) - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 6) { [weak self] in - self?.pageIndicator2.setComplete() - self?.setProgressSegmentComplete(self?.progressSegment2) - self?.pageIndicator3.setInprogress(pageNumber: 3) - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 9) { [weak self] in - self?.pageIndicator3.setComplete() - self?.setProgressSegmentComplete(self?.progressSegment3) - self?.pageIndicator4.setInprogress(pageNumber: 4) - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 12) { [weak self] in - self?.pageIndicator4.setComplete() - self?.setProgressSegmentComplete(self?.progressSegment4) - self?.pageIndicator5.setInprogress(pageNumber: 5) - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 15) { [weak self] in - self?.pageIndicator5.setComplete() - } - */ + DependencyManager.shared.activityService.$addressesWithPendingOperation + .dropFirst() + .sink { [weak self] addresses in + guard let address = DependencyManager.shared.selectedWalletAddress else { + return + } + + DispatchQueue.main.async { [weak self] in + if addresses.contains([address]) { + self?.showLoadingView() + self?.updateLoadingViewStatusLabel(message: "Waiting for transaction to complete \n\nThis should only take a few seconds") + + } else { + self?.hideLoadingView() + self?.handleOperationComplete() + } + } + }.store(in: &bag) } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - - //NotificationCenter.default.removeObserver(self, name: ChooseBakerViewController.notificationNameBakerChosen, object: nil) + } + + @IBAction func closeTapped(_ sender: Any) { + self.navigationController?.popToDetails() } func setProgressSegmentComplete(_ view: UIProgressView?) { @@ -83,7 +71,6 @@ class StakeOnboardingContainerViewController: UIViewController { override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "embed", let dest = segue.destination as? UINavigationController { childNavigationController = dest - } } @@ -94,24 +81,65 @@ class StakeOnboardingContainerViewController: UIViewController { } currentChildViewController = currentChildVc + currentStep = currentChildVc.title ?? "" + switch currentChildVc.title { case "step1": currentChildVc.performSegue(withIdentifier: "next", sender: nil) - self.pageIndicator1.setComplete() - self.setProgressSegmentComplete(self.progressSegment1) - self.pageIndicator2.setInprogress(pageNumber: 2) + self.pageIndicator1.setInprogress(pageNumber: 1) case "step2": if handlePageControllerNext(vc: currentChildVc) == true { actionButton.setTitle("Choose Baker", for: .normal) - self.pageIndicator2.setComplete() - self.setProgressSegmentComplete(self.progressSegment2) - self.pageIndicator3.setInprogress(pageNumber: 3) + self.pageIndicator1.setComplete() + self.setProgressSegmentComplete(self.progressSegment1) + self.pageIndicator2.setInprogress(pageNumber: 2) } case "step3": self.performSegue(withIdentifier: "chooseBaker", sender: nil) + case "step4": + currentChildVc.performSegue(withIdentifier: "next", sender: nil) + + case "step5": + if handlePageControllerNext(vc: currentChildVc) == true { + actionButton.setTitle("Stake", for: .normal) + self.pageIndicator3.setComplete() + self.setProgressSegmentComplete(self.progressSegment3) + self.pageIndicator4.setInprogress(pageNumber: 4) + } + + case "step6": + TransactionService.shared.currentTransactionType = .stake + TransactionService.shared.stakeData.chosenToken = Token.xtz(withAmount: DependencyManager.shared.balanceService.account.xtzBalance) + // chosenBaker will be set inside the the delegation flow + self.performSegue(withIdentifier: "stake", sender: nil) + + case "step7": + self.navigationController?.popToDetails() + + default: + self.windowError(withTitle: "error".localized(), description: "Unknown error") + } + } + + private func handleOperationComplete() { + switch currentStep { + case "step3": + self.pageIndicator2.setComplete() + self.setProgressSegmentComplete(self.progressSegment2) + self.pageIndicator3.setInprogress(pageNumber: 3) + self.currentChildViewController?.performSegue(withIdentifier: "next", sender: nil) + self.actionButton.setTitle("Next", for: .normal) + + case "step6": + self.pageIndicator4.setComplete() + self.setProgressSegmentComplete(self.progressSegment4) + self.pageIndicator5.setInprogress(pageNumber: 5) + self.currentChildViewController?.performSegue(withIdentifier: "next", sender: nil) + self.actionButton.setTitle("Done", for: .normal) + default: self.windowError(withTitle: "error".localized(), description: "Unknown error") } @@ -131,13 +159,4 @@ class StakeOnboardingContainerViewController: UIViewController { return false } } - - /* - @objc private func bakerConfirmation() { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in - self?.hideLoadingView() - self?.performSegue(withIdentifier: "confirmBaker", sender: nil) - } - } - */ }