From b3ccd52b31ad78695365d41fb135eb0bd1996d07 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Tue, 3 Dec 2024 11:41:38 +0000 Subject: [PATCH 1/5] bug fix token details learn more --- Kukai Mobile.xcodeproj/project.pbxproj | 6 ++--- .../Modules/Account/Account.storyboard | 22 +++++++++---------- .../TokenDetailsLearnMoreViewController.swift | 6 +++++ .../Modules/Fees/FeeInfoViewController.swift | 6 ++++- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index 4b58173f..9d3f4901 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -2525,7 +2525,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ""; OTHER_SWIFT_FLAGS = ""; @@ -2568,7 +2568,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2.0; OTHER_CFLAGS = ""; OTHER_SWIFT_FLAGS = "-Xfrontend -internalize-at-link"; PRODUCT_BUNDLE_IDENTIFIER = "app.kukai.mobile${BUNDLE_ID_SUFFIX}"; @@ -2755,7 +2755,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.2.0; OTHER_CFLAGS = ""; OTHER_SWIFT_FLAGS = "-Xfrontend -internalize-at-link"; PRODUCT_BUNDLE_IDENTIFIER = "app.kukai.mobile${BUNDLE_ID_SUFFIX}"; diff --git a/Kukai Mobile/Modules/Account/Account.storyboard b/Kukai Mobile/Modules/Account/Account.storyboard index 5780ce26..4df6d612 100644 --- a/Kukai Mobile/Modules/Account/Account.storyboard +++ b/Kukai Mobile/Modules/Account/Account.storyboard @@ -1987,7 +1987,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + @@ -1995,7 +1995,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + @@ -2003,7 +2003,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + @@ -2011,7 +2011,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + @@ -2019,7 +2019,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + @@ -2027,7 +2027,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + @@ -2035,7 +2035,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + @@ -2043,7 +2043,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + @@ -2051,7 +2051,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + @@ -2059,7 +2059,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + @@ -2067,7 +2067,7 @@ The estimate uses exchange data that might not be up to date and does not take i - + diff --git a/Kukai Mobile/Modules/Account/TokenDetailsLearnMoreViewController.swift b/Kukai Mobile/Modules/Account/TokenDetailsLearnMoreViewController.swift index d2c19eac..b986f20d 100644 --- a/Kukai Mobile/Modules/Account/TokenDetailsLearnMoreViewController.swift +++ b/Kukai Mobile/Modules/Account/TokenDetailsLearnMoreViewController.swift @@ -57,6 +57,12 @@ class TokenDetailsLearnMoreViewController: UIViewController { viewModel.refresh(animate: true) } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if let dest = segue.destination as? FeeInfoViewController { + dest.addGradient = true + } + } } extension TokenDetailsLearnMoreViewController: UITableViewDelegate { diff --git a/Kukai Mobile/Modules/Fees/FeeInfoViewController.swift b/Kukai Mobile/Modules/Fees/FeeInfoViewController.swift index 2af3617a..c720551d 100644 --- a/Kukai Mobile/Modules/Fees/FeeInfoViewController.swift +++ b/Kukai Mobile/Modules/Fees/FeeInfoViewController.swift @@ -14,9 +14,13 @@ class FeeInfoViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = .clear + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) if addGradient { GradientView.add(toView: self.view, withType: .fullScreenBackground) } - } + } } From 3d24f49af5fd41f08146ff3494e6c19ef0c35bb2 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Tue, 3 Dec 2024 15:46:31 +0000 Subject: [PATCH 2/5] - new public baker cell with delegation and staking info - new public baker details - updated formatting func to allow negative numbers - display negatives on all baking related UI --- Kukai Mobile.xcodeproj/project.pbxproj | 8 + .../Modules/Account/AccountViewModel.swift | 6 +- .../Account/Cells/TokenDetailsBakerCell.swift | 2 +- .../Activity/Cells/ActivityItemCell.swift | 2 +- .../Stake/BakerDetailsViewController.swift | 45 +-- .../Modules/Stake/BakerDetailsViewModel.swift | 113 ++++++ .../Cells/PublicBakerAttributeCell.swift | 25 ++ .../Modules/Stake/Cells/PublicBakerCell.swift | 47 ++- .../ChooseBakerConfirmViewController.swift | 2 +- Kukai Mobile/Modules/Stake/Stake.storyboard | 339 +++++++++--------- .../Stake/StakeAmountViewController.swift | 2 +- .../Stake/StakeConfirmViewController.swift | 2 +- Kukai Mobile/Services/CoinGeckoService.swift | 12 +- 13 files changed, 395 insertions(+), 210 deletions(-) create mode 100644 Kukai Mobile/Modules/Stake/BakerDetailsViewModel.swift create mode 100644 Kukai Mobile/Modules/Stake/Cells/PublicBakerAttributeCell.swift diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index 9d3f4901..dffa8de4 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -104,6 +104,8 @@ C02F3CB22BFF5B6300FA6383 /* AccountsAddHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02F3CB12BFF5B6300FA6383 /* AccountsAddHeaderCell.swift */; }; C02F3CB42BFF5B8E00FA6383 /* AccountsAddOptionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02F3CB32BFF5B8E00FA6383 /* AccountsAddOptionCell.swift */; }; C02F3CB62BFF77A900FA6383 /* AddWalletViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02F3CB52BFF77A900FA6383 /* AddWalletViewModel.swift */; }; + C03016042CFF498D0058A457 /* PublicBakerAttributeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03016032CFF498D0058A457 /* PublicBakerAttributeCell.swift */; }; + C03016062CFF49D90058A457 /* BakerDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03016052CFF49D90058A457 /* BakerDetailsViewModel.swift */; }; C031D3E927D114A600EABBE6 /* TokenDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C031D3E827D114A600EABBE6 /* TokenDetailsViewController.swift */; }; C031D3EB27D114BC00EABBE6 /* TokenDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C031D3EA27D114BC00EABBE6 /* TokenDetailsViewModel.swift */; }; C031D3ED27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C031D3EC27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift */; }; @@ -545,6 +547,8 @@ C02F3CB12BFF5B6300FA6383 /* AccountsAddHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddHeaderCell.swift; sourceTree = ""; }; C02F3CB32BFF5B8E00FA6383 /* AccountsAddOptionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddOptionCell.swift; sourceTree = ""; }; C02F3CB52BFF77A900FA6383 /* AddWalletViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWalletViewModel.swift; sourceTree = ""; }; + C03016032CFF498D0058A457 /* PublicBakerAttributeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicBakerAttributeCell.swift; sourceTree = ""; }; + C03016052CFF49D90058A457 /* BakerDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BakerDetailsViewModel.swift; sourceTree = ""; }; C031D3E827D114A600EABBE6 /* TokenDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsViewController.swift; sourceTree = ""; }; C031D3EA27D114BC00EABBE6 /* TokenDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsViewModel.swift; sourceTree = ""; }; C031D3EC27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectiblesDetailsViewModel.swift; sourceTree = ""; }; @@ -1539,6 +1543,7 @@ children = ( C009308E28CB3DC500763885 /* ChooseBakerHeadingCell.swift */, C0C6B5CC28CA25BD00368AEA /* PublicBakerCell.swift */, + C03016032CFF498D0058A457 /* PublicBakerAttributeCell.swift */, ); path = Cells; sourceTree = ""; @@ -1637,6 +1642,7 @@ C0717B1D2A697D3D007F9419 /* EnterCustomBakerViewController.swift */, C034F2C22A6A913B000F4C4C /* ChooseBakerConfirmViewController.swift */, C034F2C42A6AD2E5000F4C4C /* BakerDetailsViewController.swift */, + C03016052CFF49D90058A457 /* BakerDetailsViewModel.swift */, C0DB694A2CEE40D3000C1A17 /* StakeAmountViewController.swift */, C0DB694C2CEF567A000C1A17 /* StakeConfirmViewController.swift */, C04BA1F52CF765B900951249 /* StakeOnboardingContainerViewController.swift */, @@ -2037,6 +2043,7 @@ C057CF622AB0BC0900E82D41 /* UIDevice+extensions.swift in Sources */, C04E2868293F94EA00DC4171 /* TokenDetailsStakingRewardsCell.swift in Sources */, C0FBC891292C12E200B29921 /* FavouriteBalancesViewController.swift in Sources */, + C03016042CFF498D0058A457 /* PublicBakerAttributeCell.swift in Sources */, C02A4FDA27DA421100F42DE4 /* Date+extensions.swift in Sources */, C0248CEE2AA21B5900B1F63C /* String+extensions_shared.swift in Sources */, C003CECD27FDED5D00F64B4C /* CKRecord+extensions.swift in Sources */, @@ -2166,6 +2173,7 @@ C049E74028819E3800887B64 /* DiscoverViewModel.swift in Sources */, C0C6B5CF28CA27AF00368AEA /* ChooseBakerViewController.swift in Sources */, C03BF88D2A279836003BD343 /* ActivityItemBatchCell.swift in Sources */, + C03016062CFF49D90058A457 /* BakerDetailsViewModel.swift in Sources */, C04BA1F02CF4CFBE00951249 /* LearnMoreItemCell.swift in Sources */, C01372B229B206FF0083E297 /* MenuHeaderCell.swift in Sources */, C06AEDFF2C00998D005CFDAA /* AddAccountViewModel.swift in Sources */, diff --git a/Kukai Mobile/Modules/Account/AccountViewModel.swift b/Kukai Mobile/Modules/Account/AccountViewModel.swift index 69d4d13f..84d8087e 100644 --- a/Kukai Mobile/Modules/Account/AccountViewModel.swift +++ b/Kukai Mobile/Modules/Account/AccountViewModel.swift @@ -125,7 +125,7 @@ class AccountViewModel: ViewModel, UITableViewDiffableDataSourceHandler { } else if let amount = item.base as? XTZAmount, let cell = tableView.dequeueReusableCell(withIdentifier: "TokenBalanceCell", for: indexPath) as? TokenBalanceCell { cell.symbolLabel.text = "XTZ" - cell.balanceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(amount.toNormalisedDecimal() ?? 0, decimalPlaces: amount.decimalPlaces) + cell.balanceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(amount.toNormalisedDecimal() ?? 0, decimalPlaces: amount.decimalPlaces, allowNegative: false) cell.favCorner.isHidden = false // cell.setPriceChange(value: 100) // Will be re-added when we have the actual values @@ -136,7 +136,7 @@ class AccountViewModel: ViewModel, UITableViewDiffableDataSourceHandler { } 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.balanceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(obj.tez.toNormalisedDecimal() ?? 0, decimalPlaces: obj.tez.decimalPlaces, allowNegative: false) cell.favCorner.isHidden = false // cell.setPriceChange(value: 100) // Will be re-added when we have the actual values @@ -153,7 +153,7 @@ class AccountViewModel: ViewModel, UITableViewDiffableDataSourceHandler { cell.favCorner.isHidden = !token.isFavourite cell.symbolLabel.text = symbol - cell.balanceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(token.balance.toNormalisedDecimal() ?? 0, decimalPlaces: token.decimalPlaces) + cell.balanceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(token.balance.toNormalisedDecimal() ?? 0, decimalPlaces: token.decimalPlaces, allowNegative: false) // cell.setPriceChange(value: Decimal(Int.random(in: -100..<100))) // Will be re-added when we have the actual values if let tokenValueAndRate = DependencyManager.shared.balanceService.tokenValueAndRate[token.id] { diff --git a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift index 47ab96f3..86e482da 100644 --- a/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift +++ b/Kukai Mobile/Modules/Account/Cells/TokenDetailsBakerCell.swift @@ -64,7 +64,7 @@ class TokenDetailsBakerCell: UITableViewCell { } - freeSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(data.freeSpace, decimalPlaces: 6, includeThousand: true, maximumFractionDigits: 0) + freeSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(data.freeSpace, decimalPlaces: 6, includeThousand: true, allowNegative: true, maximumFractionDigits: 0) if data.freeSpace > 0 && data.enoughSpaceForBalance { freeSpaceTitleLabel.textColor = .colorNamed("Txt10") freeSpaceValueLabel.textColor = .colorNamed("Txt8") diff --git a/Kukai Mobile/Modules/Activity/Cells/ActivityItemCell.swift b/Kukai Mobile/Modules/Activity/Cells/ActivityItemCell.swift index fb185d63..b97864e1 100644 --- a/Kukai Mobile/Modules/Activity/Cells/ActivityItemCell.swift +++ b/Kukai Mobile/Modules/Activity/Cells/ActivityItemCell.swift @@ -136,7 +136,7 @@ class ActivityItemCell: UITableViewCell, UITableViewCellImageDownloading, Activi iconView.customCornerRadius = 20 let token = data.primaryToken - let balanceDsiplay = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(token?.balance.toNormalisedDecimal() ?? 0, decimalPlaces: token?.decimalPlaces ?? 6) + let balanceDsiplay = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(token?.balance.toNormalisedDecimal() ?? 0, decimalPlaces: token?.decimalPlaces ?? 6, allowNegative: false) if data.subType == .stake { titleLabel.text = "Stake: \(balanceDsiplay) XTZ" diff --git a/Kukai Mobile/Modules/Stake/BakerDetailsViewController.swift b/Kukai Mobile/Modules/Stake/BakerDetailsViewController.swift index 70c6e311..bf49a14c 100644 --- a/Kukai Mobile/Modules/Stake/BakerDetailsViewController.swift +++ b/Kukai Mobile/Modules/Stake/BakerDetailsViewController.swift @@ -7,18 +7,19 @@ import UIKit import KukaiCoreSwift +import Combine class BakerDetailsViewController: UIViewController { @IBOutlet weak var bakerIcon: UIImageView! @IBOutlet weak var bakerNameLabel: UILabel! - @IBOutlet weak var splitLabel: UILabel! - @IBOutlet weak var spaceLabel: UILabel! - @IBOutlet weak var rewardslabel: UILabel! - @IBOutlet weak var freeLabel: UILabel! + @IBOutlet weak var tableView: UITableView! @IBOutlet weak var delegateButton: CustomisableButton! + private var viewModel = BakerDetailsViewModel() + private var cancellable: AnyCancellable? + var dimBackground: Bool = false override func viewDidLoad() { @@ -26,6 +27,22 @@ class BakerDetailsViewController: UIViewController { GradientView.add(toView: self.view, withType: .fullScreenBackground) delegateButton.customButtonType = .primary + + viewModel.makeDataSource(withTableView: tableView) + tableView.dataSource = viewModel.dataSource + + cancellable = viewModel.$state.sink { [weak self] state in + switch state { + case .loading: + let _ = "" + + case .failure(_, let errorString): + self?.windowError(withTitle: "error".localized(), description: errorString) + + case .success(_): + let _ = "" + } + } } override func viewWillAppear(_ animated: Bool) { @@ -36,14 +53,10 @@ class BakerDetailsViewController: UIViewController { } delegateButton.isHidden = DependencyManager.shared.balanceService.account.delegate?.address == baker.address - + bakerNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() MediaProxyService.load(url: baker.logo, to: bakerIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) - bakerNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() - splitLabel.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" - spaceLabel.text = baker.delegation.capacity.rounded(scale: 0, roundingMode: .bankers).description + " XTZ" - rewardslabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" - freeLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0) + " XTZ" + viewModel.refresh(animate: false) } @IBAction func closeTapped(_ sender: Any) { @@ -56,15 +69,3 @@ class BakerDetailsViewController: UIViewController { self.dismissBottomSheet() } } - -extension BakerDetailsViewController: BottomSheetCustomCalculateProtocol { - - func bottomSheetHeight() -> CGFloat { - viewDidLoad() - - view.setNeedsLayout() - view.layoutIfNeeded() - - return view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height - } -} diff --git a/Kukai Mobile/Modules/Stake/BakerDetailsViewModel.swift b/Kukai Mobile/Modules/Stake/BakerDetailsViewModel.swift new file mode 100644 index 00000000..dc87c469 --- /dev/null +++ b/Kukai Mobile/Modules/Stake/BakerDetailsViewModel.swift @@ -0,0 +1,113 @@ +// +// BakerDetailsViewModel.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 03/12/2024. +// + +import UIKit +import KukaiCoreSwift +import Combine +import OSLog + +struct PublicBakerAttributeData: Hashable { + let id = UUID() + let title: String + let value: String + let valueWarning: Bool +} + +class BakerDetailsViewModel: ViewModel, UITableViewDiffableDataSourceHandler { + + typealias SectionEnum = Int + typealias CellDataType = AnyHashableSendable + + private var bag = [AnyCancellable]() + private var currentSnapshot = NSDiffableDataSourceSnapshot() + + var dataSource: UITableViewDiffableDataSource? = nil + + + + // MARK: - Init + + override init() { + super.init() + } + + deinit { + bag.forEach({ $0.cancel() }) + } + + + + // MARK: - Functions + + func makeDataSource(withTableView tableView: UITableView) { + dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, item in + + if let obj = item.base as? ChooseBakerHeaderData, let cell = tableView.dequeueReusableCell(withIdentifier: "ChooseBakerHeadingCell", for: indexPath) as? ChooseBakerHeadingCell { + cell.headingLabel.text = obj.title + return cell + + } else if let obj = item.base as? PublicBakerAttributeData, let cell = tableView.dequeueReusableCell(withIdentifier: "PublicBakerAttributeCell", for: indexPath) as? PublicBakerAttributeCell { + cell.setup(data: obj) + return cell + + } else { + return UITableViewCell() + } + }) + + dataSource?.defaultRowAnimation = .fade + } + + func refresh(animate: Bool, successMessage: String? = nil) { + guard let ds = dataSource, let baker = TransactionService.shared.delegateData.chosenBaker else { + state = .failure(KukaiError.unknown(withString: "Unable to locate wallet"), "Unable to find datasource") + return + } + + // Build snapshot + self.currentSnapshot = NSDiffableDataSourceSnapshot() + + if baker.delegation.enabled { + let sectionIndex = self.currentSnapshot.numberOfSections + self.currentSnapshot.appendSections([ sectionIndex ]) + + let freeSpace = baker.delegation.freeSpace + let capacity = baker.delegation.capacity + let minBalance = baker.delegation.minBalance + self.currentSnapshot.appendItems([ + .init(ChooseBakerHeaderData(title: "Delegation", actionTitle: nil)), + .init(PublicBakerAttributeData(title: "Split:", value: (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%", valueWarning: false)), + .init(PublicBakerAttributeData(title: "Est APY:", value: (Decimal(baker.delegation.estimatedApy) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%", valueWarning: false)), + .init(PublicBakerAttributeData(title: "Free Space:", value: DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ", valueWarning: freeSpace < .zero)), + .init(PublicBakerAttributeData(title: "Capacity:", value: DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(capacity, decimalPlaces: 0, allowNegative: true) + " XTZ", valueWarning: capacity < .zero)), + .init(PublicBakerAttributeData(title: "Min Balance", value: DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(minBalance, decimalPlaces: 0, allowNegative: true) + " XTZ", valueWarning: minBalance < .zero)) + ], toSection: sectionIndex) + } + + if baker.staking.enabled { + let sectionIndex = self.currentSnapshot.numberOfSections + self.currentSnapshot.appendSections([ sectionIndex ]) + + let freeSpace = baker.staking.freeSpace + let capacity = baker.staking.capacity + let minBalance = baker.staking.minBalance + self.currentSnapshot.appendItems([ + .init(ChooseBakerHeaderData(title: "Delegation", actionTitle: nil)), + .init(PublicBakerAttributeData(title: "Split:", value: (Decimal(baker.staking.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%", valueWarning: false)), + .init(PublicBakerAttributeData(title: "Est APY:", value: (Decimal(baker.staking.estimatedApy) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%", valueWarning: false)), + .init(PublicBakerAttributeData(title: "Free Space:", value: DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ", valueWarning: freeSpace < .zero)), + .init(PublicBakerAttributeData(title: "Capacity:", value: DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(capacity, decimalPlaces: 0, allowNegative: true) + " XTZ", valueWarning: capacity < .zero)), + .init(PublicBakerAttributeData(title: "Min Balance", value: DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(minBalance, decimalPlaces: 0, allowNegative: true) + " XTZ", valueWarning: minBalance < .zero)) + ], toSection: sectionIndex) + } + + ds.apply(currentSnapshot, animatingDifferences: animate) + + // Return success + self.state = .success(successMessage) + } +} diff --git a/Kukai Mobile/Modules/Stake/Cells/PublicBakerAttributeCell.swift b/Kukai Mobile/Modules/Stake/Cells/PublicBakerAttributeCell.swift new file mode 100644 index 00000000..9e760c13 --- /dev/null +++ b/Kukai Mobile/Modules/Stake/Cells/PublicBakerAttributeCell.swift @@ -0,0 +1,25 @@ +// +// PublicBakerAttributeCell.swift +// Kukai Mobile +// +// Created by Simon Mcloughlin on 03/12/2024. +// + +import UIKit + +class PublicBakerAttributeCell: UITableViewCell { + + @IBOutlet weak var attributeTitleLabel: UILabel! + @IBOutlet weak var attributeLabel: UILabel! + + public func setup(data: PublicBakerAttributeData) { + attributeTitleLabel.text = data.title + attributeLabel.text = data.value + + if data.valueWarning { + attributeLabel.textColor = .colorNamed("TxtAlert4") + } else { + attributeLabel.textColor = .colorNamed("Txt8") + } + } +} diff --git a/Kukai Mobile/Modules/Stake/Cells/PublicBakerCell.swift b/Kukai Mobile/Modules/Stake/Cells/PublicBakerCell.swift index 0f26b89c..66cf280b 100644 --- a/Kukai Mobile/Modules/Stake/Cells/PublicBakerCell.swift +++ b/Kukai Mobile/Modules/Stake/Cells/PublicBakerCell.swift @@ -13,9 +13,14 @@ class PublicBakerCell: UITableViewCell, UITableViewCellImageDownloading { @IBOutlet weak var bakerIcon: SDAnimatedImageView! @IBOutlet weak var bakerNameLabel: UILabel! - @IBOutlet weak var splitLabel: UILabel! - @IBOutlet weak var spaceLabel: UILabel! - @IBOutlet weak var estRewardsLabel: UILabel! + + @IBOutlet weak var delegationSplit: UILabel! + @IBOutlet weak var delegationAPY: UILabel! + @IBOutlet weak var delegationFreeSpace: UILabel! + + @IBOutlet weak var stakingSplit: UILabel! + @IBOutlet weak var stakingAPY: UILabel! + @IBOutlet weak var stakingFreeSpace: UILabel! override func awakeFromNib() { super.awakeFromNib() @@ -24,11 +29,39 @@ class PublicBakerCell: UITableViewCell, UITableViewCellImageDownloading { public func setup(withBaker baker: TzKTBaker) { MediaProxyService.load(url: baker.logo, to: bakerIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) - bakerNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() - splitLabel.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" - spaceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0) + " XTZ" - estRewardsLabel.text = Decimal((baker.delegation.estimatedApy * 100)).rounded(scale: 2, roundingMode: .bankers).description + "%" + + if baker.delegation.enabled { + delegationSplit.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + delegationAPY.text = Decimal((baker.delegation.estimatedApy * 100)).rounded(scale: 2, roundingMode: .bankers).description + "%" + delegationFreeSpace.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0, includeThousand: true, allowNegative: true) + " XTZ" + + if baker.delegation.freeSpace < .zero { + delegationFreeSpace.textColor = .colorNamed("TxtAlert4") + } else { + delegationFreeSpace.textColor = .colorNamed("Txt8") + } + } else { + delegationSplit.text = "N/A" + delegationAPY.text = "N/A" + delegationFreeSpace.text = "N/A" + } + + if baker.staking.enabled { + stakingSplit.text = (Decimal(baker.staking.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + stakingAPY.text = Decimal((baker.staking.estimatedApy * 100)).rounded(scale: 2, roundingMode: .bankers).description + "%" + stakingFreeSpace.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.staking.freeSpace, decimalPlaces: 0, includeThousand: true, allowNegative: true) + " XTZ" + + if baker.staking.freeSpace < .zero { + stakingFreeSpace.textColor = .colorNamed("TxtAlert4") + } else { + stakingFreeSpace.textColor = .colorNamed("Txt8") + } + } else { + stakingSplit.text = "N/A" + stakingAPY.text = "N/A" + stakingFreeSpace.text = "N/A" + } bakerNameLabel.accessibilityIdentifier = "baker-list-name" } diff --git a/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift b/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift index d11a502b..606ade12 100644 --- a/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift +++ b/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift @@ -124,7 +124,7 @@ class ChooseBakerConfirmViewController: SendAbstractConfirmViewController, Slide } 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" + bakerAddSpaceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ" bakerAddEstimatedRewardLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" } diff --git a/Kukai Mobile/Modules/Stake/Stake.storyboard b/Kukai Mobile/Modules/Stake/Stake.storyboard index fdc72c24..f19e79b8 100644 --- a/Kukai Mobile/Modules/Stake/Stake.storyboard +++ b/Kukai Mobile/Modules/Stake/Stake.storyboard @@ -96,73 +96,101 @@ - - + + - - + + - - - - - - - - - - + + + + + + + + + - - - - - + + + + + + + + @@ -247,13 +286,13 @@ - + - + @@ -545,112 +584,11 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -678,19 +686,19 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + - + - - + + @@ -700,15 +708,12 @@ 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 740a2a3c..b09fa309 100644 --- a/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift @@ -58,7 +58,7 @@ class StakeAmountViewController: UIViewController { MediaProxyService.load(url: baker.logo, to: bakerIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) bakerNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() bakerSplitValueLabel.text = "\(baker.staking.fee * 100)%" - bakerSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.staking.freeSpace, decimalPlaces: token.decimalPlaces, includeThousand: true, maximumFractionDigits: 0) + bakerSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.staking.freeSpace, decimalPlaces: token.decimalPlaces, includeThousand: true, allowNegative: true, maximumFractionDigits: 0) bakerRewardsValueLabel.text = "\(baker.staking.estimatedApy * 100)%" diff --git a/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift index 7bcc977e..bbe0f5d5 100644 --- a/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift @@ -150,7 +150,7 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton } else { 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" + bakerSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ" bakerRewardsValueLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" } diff --git a/Kukai Mobile/Services/CoinGeckoService.swift b/Kukai Mobile/Services/CoinGeckoService.swift index 92f0c6b5..5e45d2bd 100644 --- a/Kukai Mobile/Services/CoinGeckoService.swift +++ b/Kukai Mobile/Services/CoinGeckoService.swift @@ -323,11 +323,11 @@ public class CoinGeckoService { return numberFormatter.string(from: 0.00) ?? "0" } - public func format(decimal: Decimal, numberStyle: NumberFormatter.Style, maximumFractionDigits: Int? = nil) -> String { + public func format(decimal: Decimal, numberStyle: NumberFormatter.Style, allowNegative: Bool = false, maximumFractionDigits: Int? = nil) -> String { let numberFormatter = sharedNumberFormatter() numberFormatter.numberStyle = numberStyle - guard decimal >= 0 else { + guard decimal >= 0 || allowNegative else { if numberStyle == .decimal { return dashedString() } else { @@ -348,11 +348,11 @@ public class CoinGeckoService { return outputString } - func formatLargeTokenDisplay(_ num: Decimal, decimalPlaces: Int, includeThousand: Bool = false, maximumFractionDigits: Int = 3) -> String { + func formatLargeTokenDisplay(_ num: Decimal, decimalPlaces: Int, includeThousand: Bool = false, allowNegative: Bool, maximumFractionDigits: Int = 3) -> String { var reducedNumber: Decimal = 0 var reducedNumberSymbol: String? = nil - switch num { + switch abs(num) { case 1_000_000_000_000...: reducedNumber = num / 1_000_000_000 reducedNumberSymbol = "t" @@ -378,11 +378,11 @@ public class CoinGeckoService { var stringToReturn = "" if let symbol = reducedNumberSymbol { - stringToReturn = format(decimal: reducedNumber, numberStyle: .decimal, maximumFractionDigits: maximumFractionDigits) + stringToReturn = format(decimal: reducedNumber, numberStyle: .decimal, allowNegative: allowNegative, maximumFractionDigits: maximumFractionDigits) stringToReturn += symbol } else { - stringToReturn = format(decimal: reducedNumber, numberStyle: .decimal, maximumFractionDigits: decimalPlaces) + stringToReturn = format(decimal: reducedNumber, numberStyle: .decimal, allowNegative: allowNegative, maximumFractionDigits: decimalPlaces) } return stringToReturn From 35e5b8f8e011e395eaacef30a1ab671938a307d6 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Tue, 3 Dec 2024 16:27:33 +0000 Subject: [PATCH 3/5] tweak baker order and filter --- Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift b/Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift index 24e5d8a1..cabb4299 100644 --- a/Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift +++ b/Kukai Mobile/Modules/Stake/ChooseBakerViewModel.swift @@ -67,7 +67,7 @@ class ChooseBakerViewModel: ViewModel, UITableViewDiffableDataSourceHandler { } func refresh(animate: Bool, successMessage: String? = nil) { - guard let ds = dataSource, let xtzBalanceAsDecimal = DependencyManager.shared.balanceService.account.xtzBalance.toNormalisedDecimal() else { + guard let ds = dataSource, let xtzAvailableBalance = DependencyManager.shared.balanceService.account.availableBalance.toNormalisedDecimal() else { state = .failure(KukaiError.unknown(withString: "Unable to locate wallet"), "Unable to find datasource") return } @@ -92,11 +92,11 @@ class ChooseBakerViewModel: ViewModel, UITableViewDiffableDataSourceHandler { return false } - return baker.delegation.capacity > xtzBalanceAsDecimal && baker.status == TzKTBakerStatus.active + return baker.delegation.capacity > xtzAvailableBalance && baker.staking.capacity > xtzAvailableBalance && baker.status == TzKTBakerStatus.active } let sortedResults = filteredResults.sorted(by: { lhs, rhs in - lhs.delegation.estimatedApy > rhs.delegation.estimatedApy + lhs.staking.estimatedApy > rhs.staking.estimatedApy }) From b74666ef96972c634d29e296baa9820eda51ebd9 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Tue, 3 Dec 2024 17:27:18 +0000 Subject: [PATCH 4/5] change stake confirmation modals to use new public baker cell style layout --- .../ChooseBakerConfirmViewController.swift | 40 +- Kukai Mobile/Modules/Stake/Stake.storyboard | 379 ++++++++++++------ .../Stake/StakeAmountViewController.swift | 41 +- .../Stake/StakeConfirmViewController.swift | 40 +- 4 files changed, 347 insertions(+), 153 deletions(-) diff --git a/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift b/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift index 606ade12..5c6a0247 100644 --- a/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift +++ b/Kukai Mobile/Modules/Stake/ChooseBakerConfirmViewController.swift @@ -31,9 +31,12 @@ class ChooseBakerConfirmViewController: SendAbstractConfirmViewController, Slide @IBOutlet weak var confirmBakerAddView: UIView! @IBOutlet weak var bakerAddIcon: UIImageView! @IBOutlet weak var bakerAddNameLabel: UILabel! - @IBOutlet weak var bakerAddSplitLabel: UILabel! - @IBOutlet weak var bakerAddSpaceLabel: UILabel! - @IBOutlet weak var bakerAddEstimatedRewardLabel: UILabel! + @IBOutlet weak var bakerAddDelegationSplitLabel: UILabel! + @IBOutlet weak var bakerAddDelegationApyLabel: UILabel! + @IBOutlet weak var bakerAddDelegationFreeSpaceLabel: UILabel! + @IBOutlet weak var bakerAddStakingSplitLabel: UILabel! + @IBOutlet weak var bakerAddStakingApyLabel: UILabel! + @IBOutlet weak var bakerAddStakingFreeSpaceLabel: UILabel! @IBOutlet weak var confirmBakerRemoveView: UIView! @IBOutlet weak var bakerRemoveIcon: UIImageView! @@ -118,14 +121,33 @@ class ChooseBakerConfirmViewController: SendAbstractConfirmViewController, Slide 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" + bakerAddDelegationSplitLabel.text = "N/A" + bakerAddDelegationApyLabel.text = "N/A" + bakerAddDelegationFreeSpaceLabel.text = "N/A" + bakerAddStakingSplitLabel.text = "N/A" + bakerAddStakingApyLabel.text = "N/A" + bakerAddStakingFreeSpaceLabel.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, allowNegative: true) + " XTZ" - bakerAddEstimatedRewardLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerAddDelegationSplitLabel.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerAddDelegationApyLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerAddDelegationFreeSpaceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ" + + if baker.delegation.freeSpace < 0 { + bakerAddDelegationFreeSpaceLabel.textColor = .colorNamed("TxtAlert4") + } else { + bakerAddDelegationFreeSpaceLabel.textColor = .colorNamed("Txt8") + } + + bakerAddStakingSplitLabel.text = (Decimal(baker.staking.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerAddStakingApyLabel.text = Decimal(baker.staking.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerAddStakingFreeSpaceLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.staking.freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ" + + if baker.staking.freeSpace < 0 { + bakerAddStakingFreeSpaceLabel.textColor = .colorNamed("TxtAlert4") + } else { + bakerAddStakingFreeSpaceLabel.textColor = .colorNamed("Txt8") + } } } else { diff --git a/Kukai Mobile/Modules/Stake/Stake.storyboard b/Kukai Mobile/Modules/Stake/Stake.storyboard index f19e79b8..17148431 100644 --- a/Kukai Mobile/Modules/Stake/Stake.storyboard +++ b/Kukai Mobile/Modules/Stake/Stake.storyboard @@ -765,16 +765,16 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + - + - + - + @@ -795,54 +795,88 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - + + - - + + - - - + + - - - + + - - - + + + + + + + - - + - - + + @@ -901,13 +939,13 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1015,7 +1053,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + - - + + - - + + - - - + + - - - + + - - - + + + + + + - - - + + + - + - - + - @@ -1784,7 +1865,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -1910,13 +1991,13 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + - + - + @@ -1937,54 +2018,88 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - - + + - - + + - - - + + - - - + + - - - + + + + + + - + - - + - + + @@ -2019,7 +2138,6 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - @@ -2036,7 +2154,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 b09fa309..68361b21 100644 --- a/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeAmountViewController.swift @@ -12,9 +12,12 @@ 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 bakerDelegationSplitValueLabel: UILabel! + @IBOutlet weak var bakerDelegationApyValueLabel: UILabel! + @IBOutlet weak var bakerDelegationFreeSpaceValueLabel: UILabel! + @IBOutlet weak var bakerStakingSplitValueLabel: UILabel! + @IBOutlet weak var bakerStakingApyValueLabel: UILabel! + @IBOutlet weak var bakerStakingFreeSpaceValueLabel: UILabel! @IBOutlet weak var tokenNameLabel: UILabel! @IBOutlet weak var tokenBalanceTitleLabel: UILabel! @@ -57,9 +60,35 @@ class StakeAmountViewController: UIViewController { // To section MediaProxyService.load(url: baker.logo, to: bakerIcon, withCacheType: .temporary, fallback: UIImage.unknownToken()) bakerNameLabel.text = baker.name ?? baker.address.truncateTezosAddress() - bakerSplitValueLabel.text = "\(baker.staking.fee * 100)%" - bakerSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.staking.freeSpace, decimalPlaces: token.decimalPlaces, includeThousand: true, allowNegative: true, maximumFractionDigits: 0) - bakerRewardsValueLabel.text = "\(baker.staking.estimatedApy * 100)%" + if baker.name == nil && baker.delegation.fee == 0 && baker.delegation.capacity == 0 && baker.delegation.estimatedApy == 0 { + bakerDelegationSplitValueLabel.text = "N/A" + bakerDelegationApyValueLabel.text = "N/A" + bakerDelegationFreeSpaceValueLabel.text = "N/A" + bakerStakingSplitValueLabel.text = "N/A" + bakerStakingApyValueLabel.text = "N/A" + bakerStakingFreeSpaceValueLabel.text = "N/A" + + } else { + bakerDelegationSplitValueLabel.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerDelegationApyValueLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerDelegationFreeSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ" + + if baker.delegation.freeSpace < 0 { + bakerDelegationFreeSpaceValueLabel.textColor = .colorNamed("TxtAlert4") + } else { + bakerDelegationFreeSpaceValueLabel.textColor = .colorNamed("Txt8") + } + + bakerStakingSplitValueLabel.text = (Decimal(baker.staking.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerStakingApyValueLabel.text = Decimal(baker.staking.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerStakingFreeSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.staking.freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ" + + if baker.delegation.freeSpace < 0 { + bakerStakingFreeSpaceValueLabel.textColor = .colorNamed("TxtAlert4") + } else { + bakerStakingFreeSpaceValueLabel.textColor = .colorNamed("Txt8") + } + } // Token data diff --git a/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift index bbe0f5d5..691494b0 100644 --- a/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeConfirmViewController.swift @@ -31,9 +31,12 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton // Baker @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 bakerDelegationSplitValueLabel: UILabel! + @IBOutlet weak var bakerDelegationApyValueLabel: UILabel! + @IBOutlet weak var bakerDelegationFreeSpaceValueLabel: UILabel! + @IBOutlet weak var bakerStakingSplitValueLabel: UILabel! + @IBOutlet weak var bakerStakingApyValueLabel: UILabel! + @IBOutlet weak var bakerStakingFreeSpaceValueLabel: UILabel! // Stake @IBOutlet weak var actionTitleLabel: UILabel! @@ -144,14 +147,33 @@ class StakeConfirmViewController: SendAbstractConfirmViewController, SlideButton // Baker info config 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" + bakerDelegationSplitValueLabel.text = "N/A" + bakerDelegationApyValueLabel.text = "N/A" + bakerDelegationFreeSpaceValueLabel.text = "N/A" + bakerStakingSplitValueLabel.text = "N/A" + bakerStakingApyValueLabel.text = "N/A" + bakerStakingFreeSpaceValueLabel.text = "N/A" } else { - bakerSplitValueLabel.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" - bakerSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ" - bakerRewardsValueLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerDelegationSplitValueLabel.text = (Decimal(baker.delegation.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerDelegationApyValueLabel.text = Decimal(baker.delegation.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerDelegationFreeSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.delegation.freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ" + + if baker.delegation.freeSpace < 0 { + bakerDelegationFreeSpaceValueLabel.textColor = .colorNamed("TxtAlert4") + } else { + bakerDelegationFreeSpaceValueLabel.textColor = .colorNamed("Txt8") + } + + bakerStakingSplitValueLabel.text = (Decimal(baker.staking.fee) * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerStakingApyValueLabel.text = Decimal(baker.staking.estimatedApy * 100).rounded(scale: 2, roundingMode: .bankers).description + "%" + bakerStakingFreeSpaceValueLabel.text = DependencyManager.shared.coinGeckoService.formatLargeTokenDisplay(baker.staking.freeSpace, decimalPlaces: 0, allowNegative: true) + " XTZ" + + if baker.delegation.freeSpace < 0 { + bakerStakingFreeSpaceValueLabel.textColor = .colorNamed("TxtAlert4") + } else { + bakerStakingFreeSpaceValueLabel.textColor = .colorNamed("Txt8") + } } From fd34d38c52b41f61428ccce5d50f1992194cdbf7 Mon Sep 17 00:00:00 2001 From: Simon Mcloughlin Date: Wed, 4 Dec 2024 15:27:21 +0000 Subject: [PATCH 5/5] - add suggested action cell - enable stake onboarding to show either a 2 or 4 step process depending on users delegation status - remove 5th step indicator for completion page --- Kukai Mobile.xcodeproj/project.pbxproj | 4 + .../Modules/Account/Account.storyboard | 181 +++++++++++++----- .../Account/AccountViewController.swift | 20 ++ .../Modules/Account/AccountViewModel.swift | 82 ++++++-- .../Account/Cells/SuggestedActionCell.swift | 21 ++ Kukai Mobile/Modules/Stake/Stake.storyboard | 110 +++++------ ...akeOnboardingContainerViewController.swift | 59 ++++-- 7 files changed, 343 insertions(+), 134 deletions(-) create mode 100644 Kukai Mobile/Modules/Account/Cells/SuggestedActionCell.swift diff --git a/Kukai Mobile.xcodeproj/project.pbxproj b/Kukai Mobile.xcodeproj/project.pbxproj index dffa8de4..1b0f7673 100644 --- a/Kukai Mobile.xcodeproj/project.pbxproj +++ b/Kukai Mobile.xcodeproj/project.pbxproj @@ -106,6 +106,7 @@ C02F3CB62BFF77A900FA6383 /* AddWalletViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02F3CB52BFF77A900FA6383 /* AddWalletViewModel.swift */; }; C03016042CFF498D0058A457 /* PublicBakerAttributeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03016032CFF498D0058A457 /* PublicBakerAttributeCell.swift */; }; C03016062CFF49D90058A457 /* BakerDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03016052CFF49D90058A457 /* BakerDetailsViewModel.swift */; }; + C03016082D007DB00058A457 /* SuggestedActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C03016072D007DB00058A457 /* SuggestedActionCell.swift */; }; C031D3E927D114A600EABBE6 /* TokenDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C031D3E827D114A600EABBE6 /* TokenDetailsViewController.swift */; }; C031D3EB27D114BC00EABBE6 /* TokenDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C031D3EA27D114BC00EABBE6 /* TokenDetailsViewModel.swift */; }; C031D3ED27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C031D3EC27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift */; }; @@ -549,6 +550,7 @@ C02F3CB52BFF77A900FA6383 /* AddWalletViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWalletViewModel.swift; sourceTree = ""; }; C03016032CFF498D0058A457 /* PublicBakerAttributeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicBakerAttributeCell.swift; sourceTree = ""; }; C03016052CFF49D90058A457 /* BakerDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BakerDetailsViewModel.swift; sourceTree = ""; }; + C03016072D007DB00058A457 /* SuggestedActionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestedActionCell.swift; sourceTree = ""; }; C031D3E827D114A600EABBE6 /* TokenDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsViewController.swift; sourceTree = ""; }; C031D3EA27D114BC00EABBE6 /* TokenDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetailsViewModel.swift; sourceTree = ""; }; C031D3EC27D114D000EABBE6 /* CollectiblesDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectiblesDetailsViewModel.swift; sourceTree = ""; }; @@ -1679,6 +1681,7 @@ isa = PBXGroup; children = ( C054BCA32A682B6F006BDFBB /* BackUpCell.swift */, + C03016072D007DB00058A457 /* SuggestedActionCell.swift */, C04E0EB12ADE7EE7001BF56F /* UpdateWarningCell.swift */, C0D921D1293E49A20020E0BF /* TokenBalanceHeaderCell.swift */, C08694DD27BD1B84000A4909 /* TokenBalanceCell.swift */, @@ -2047,6 +2050,7 @@ C02A4FDA27DA421100F42DE4 /* Date+extensions.swift in Sources */, C0248CEE2AA21B5900B1F63C /* String+extensions_shared.swift in Sources */, C003CECD27FDED5D00F64B4C /* CKRecord+extensions.swift in Sources */, + C03016082D007DB00058A457 /* SuggestedActionCell.swift in Sources */, C04D985A2CE65884009491BD /* TokenDetailsStakeBalanceCell.swift in Sources */, C06AEDFD2C00997E005CFDAA /* AddAccountViewController.swift in Sources */, C04E2864293F94C500DC4171 /* TokenDetailsBalanceCell.swift in Sources */, diff --git a/Kukai Mobile/Modules/Account/Account.storyboard b/Kukai Mobile/Modules/Account/Account.storyboard index 4df6d612..738ccb33 100644 --- a/Kukai Mobile/Modules/Account/Account.storyboard +++ b/Kukai Mobile/Modules/Account/Account.storyboard @@ -29,11 +29,11 @@ - + - + @@ -48,7 +48,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -106,7 +106,7 @@ - + @@ -124,8 +124,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -213,7 +291,7 @@ - + @@ -266,7 +344,7 @@ - + @@ -329,7 +407,7 @@ - + @@ -441,7 +519,7 @@ - + @@ -544,7 +622,7 @@ - + @@ -752,29 +830,29 @@ - + - + - - + - - - - + - + + + + - - + @@ -514,7 +495,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -530,7 +511,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -2546,7 +2527,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -2568,7 +2549,7 @@ Bakers validate and participate in Tezos governance. By delegating to a Baker, y - + @@ -3187,7 +3168,7 @@ Bakers also vote on behalf of users to make changes to the network, during the G - + @@ -3211,8 +3192,8 @@ Bakers also vote on behalf of users to make changes to the network, during the G @@ -3263,7 +3244,7 @@ Bakers also vote on behalf of users to make changes to the network, during the G - + @@ -3317,7 +3298,7 @@ Bakers also vote on behalf of users to make changes to the network, during the G - + @@ -3376,7 +3357,7 @@ You can see these rewards, change your baker, stake more, unstake, or finalise y - + @@ -3392,9 +3373,28 @@ You can see these rewards, change your baker, stake more, unstake, or finalise y - + + + + + + + + + + + + + + + + + + + + diff --git a/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift index cbd8a458..0210c7f2 100644 --- a/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift +++ b/Kukai Mobile/Modules/Stake/StakeOnboardingContainerViewController.swift @@ -11,6 +11,10 @@ import Combine class StakeOnboardingContainerViewController: UIViewController { + @IBOutlet weak var indicatorStackview: UIStackView! + @IBOutlet weak var indicatorStackviewLeadingConstraint: NSLayoutConstraint! + @IBOutlet weak var indicatorStackviewTrailingConstraint: NSLayoutConstraint! + @IBOutlet weak var pageIndicator1: PageIndicatorContainerView! @IBOutlet weak var progressSegment1: UIProgressView! @IBOutlet weak var pageIndicator2: PageIndicatorContainerView! @@ -18,22 +22,52 @@ class StakeOnboardingContainerViewController: UIViewController { @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! @IBOutlet weak var actionButton: CustomisableButton! - @IBOutlet weak var navigationContainerView: UIView! + @IBOutlet weak var delegateAndStakeContainer: UIView! + @IBOutlet weak var stakeOnlyContainer: UIView! + private var childNavigationController: UINavigationController? = nil private var currentChildViewController: UIViewController? = nil private var bag = [AnyCancellable]() private var currentStep: String = "" + private var isStakeOnly = false + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + isStakeOnly = (DependencyManager.shared.balanceService.account.delegate != nil) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + + isStakeOnly = (DependencyManager.shared.balanceService.account.delegate != nil) + } override func viewDidLoad() { super.viewDidLoad() GradientView.add(toView: self.view, withType: .fullScreenBackground) - actionButton.customButtonType = .primary + + // If user only needs to stake we hide the first few screens and 2 steps + delegateAndStakeContainer.isHidden = isStakeOnly + stakeOnlyContainer.isHidden = !isStakeOnly + + if isStakeOnly { + indicatorStackview.removeArrangedSubview(pageIndicator1) + pageIndicator1.isHidden = true + indicatorStackview.removeArrangedSubview(pageIndicator2) + pageIndicator2.isHidden = true + progressSegment1.removeFromSuperview() + + // With only 2 steps it looks odd to have it the full length of the screen, reduce it a bit + indicatorStackviewLeadingConstraint.constant = 24 * 5 + indicatorStackviewTrailingConstraint.constant = 24 * 5 + } + + DependencyManager.shared.activityService.$addressesWithPendingOperation .dropFirst() .sink { [weak self] addresses in @@ -58,10 +92,6 @@ class StakeOnboardingContainerViewController: UIViewController { super.viewDidDisappear(animated) } - @IBAction func closeTapped(_ sender: Any) { - self.navigationController?.popToDetails() - } - func setProgressSegmentComplete(_ view: UIProgressView?) { UIView.animate(withDuration: 0.7) { view?.setProgress(1, animated: true) @@ -69,7 +99,10 @@ class StakeOnboardingContainerViewController: UIViewController { } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == "embed", let dest = segue.destination as? UINavigationController { + if !isStakeOnly, segue.identifier == "embed-delegate", let dest = segue.destination as? UINavigationController { + childNavigationController = dest + + } else if isStakeOnly, segue.identifier == "embed-stake", let dest = segue.destination as? UINavigationController { childNavigationController = dest } } @@ -102,12 +135,16 @@ class StakeOnboardingContainerViewController: UIViewController { case "step4": currentChildVc.performSegue(withIdentifier: "next", sender: nil) + if isStakeOnly { + self.pageIndicator3.setInprogress(pageNumber: 1) + } + case "step5": if handlePageControllerNext(vc: currentChildVc) == true { actionButton.setTitle("Stake", for: .normal) self.pageIndicator3.setComplete() self.setProgressSegmentComplete(self.progressSegment3) - self.pageIndicator4.setInprogress(pageNumber: 4) + self.pageIndicator4.setInprogress(pageNumber: isStakeOnly ? 2 : 4) } case "step6": @@ -135,8 +172,6 @@ class StakeOnboardingContainerViewController: UIViewController { 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)