From de0aed2d33928f45d6d931bfdd31072e4b57c1bd Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 22 Aug 2024 14:02:32 -0700 Subject: [PATCH 1/4] add withdraw/deposit buttons --- .../_v4/Vault/dydxVaultViewBuilder.swift | 17 ++- .../Vault/Landing/dydxVaultViewModel.swift | 130 ++++++++++++------ 2 files changed, 103 insertions(+), 44 deletions(-) diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Vault/dydxVaultViewBuilder.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/dydxVaultViewBuilder.swift index 12f96c42b..5450c45b2 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Vault/dydxVaultViewBuilder.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/dydxVaultViewBuilder.swift @@ -14,6 +14,7 @@ import RoutingKit import ParticlesKit import PlatformUI import Charts +import dydxStateManager public class dydxVaultViewBuilder: NSObject, ObjectBuilderProtocol { public func build() -> T? { @@ -40,11 +41,21 @@ private protocol dydxVaultViewBuilderPresenterProtocol: HostedViewPresenterProto private class dydxVaultViewBuilderPresenter: HostedViewPresenter, dydxVaultViewBuilderPresenterProtocol { override init() { super.init() - + viewModel = dydxVaultViewModel() viewModel?.vaultChart = dydxVaultChartViewModel() - - // TODO: remove & replace, test only + + let usdcToken = AbacusStateManager.shared.environment?.usdcTokenInfo?.denom + AbacusStateManager.shared.state.accountBalance(of: usdcToken) + .sink {[weak self] usdcBalance in + if usdcBalance ?? 0 > 0 { + self?.viewModel?.withdrawAction = {} + } + } + .store(in: &subscriptions) + viewModel?.depositAction = {} + + //TODO: remove & replace, test only Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] _ in guard let self = self else { return } self.viewModel?.vaultChart?.setEntries(entries: self.generateEntries()) diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift index 81d9a14d8..041a3e348 100644 --- a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift +++ b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift @@ -19,6 +19,8 @@ public class dydxVaultViewModel: PlatformViewModel { @Published public var positions: [dydxVaultPositionViewModel]? @Published public var cancelAction: (() -> Void)? @Published public var learnMoreAction: (() -> Void)? + @Published public var withdrawAction: (() -> Void)? + @Published public var depositAction: (() -> Void)? public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] _ in @@ -33,44 +35,49 @@ private struct dydxVaultView: View { @ObservedObject var viewModel: dydxVaultViewModel var body: some View { - VStack { - Spacer().frame(height: 12) - titleRow - Spacer().frame(height: 20) - ScrollView { - LazyVStack(pinnedViews: [.sectionHeaders]) { - VStack(spacing: 0) { - vaultPnlRow - Spacer().frame(height: 16) - div - Spacer().frame(height: 16) - aprTvlRow - Spacer().frame(height: 16) - div - Spacer().frame(height: 16) - chart - Spacer().frame(height: 16) - div - Spacer().frame(height: 16) - } - Section(header: positionsStickyHeader) { - positionsList + ZStack(alignment: .bottom) { + VStack { + Spacer().frame(height: 12) + titleRow + Spacer().frame(height: 20) + ScrollView { + LazyVStack(pinnedViews: [.sectionHeaders]) { + VStack(spacing: 0) { + vaultPnlRow + Spacer().frame(height: 16) + div + Spacer().frame(height: 16) + aprTvlRow + Spacer().frame(height: 16) + div + Spacer().frame(height: 16) + chart + Spacer().frame(height: 16) + div + Spacer().frame(height: 16) + } + Section(header: positionsStickyHeader) { + positionsList + Spacer().frame(height: 96) + } } } } + buttonStack + .padding(.bottom, 32) } .frame(maxWidth: .infinity) .themeColor(background: .layer2) } - var div: some View { + private var div: some View { Rectangle() .themeColor(foreground: .borderDefault) .frame(height: 1) } // MARK: - Header - var titleRow: some View { + private var titleRow: some View { HStack(spacing: 16) { titleImage titleText @@ -80,7 +87,7 @@ private struct dydxVaultView: View { .padding(.horizontal, 16) } - var titleImage: some View { + private var titleImage: some View { PlatformIconViewModel(type: .asset(name: "icon_token", bundle: .dydxView), clip: .noClip, size: .init(width: 40, height: 40), @@ -88,13 +95,13 @@ private struct dydxVaultView: View { .createView() } - var titleText: some View { + private var titleText: some View { Text(DataLocalizer.shared?.localize(path: "APP.VAULTS.VAULT", params: nil) ?? "") .themeColor(foreground: .textPrimary) .themeFont(fontType: .base, fontSize: .large) } - var learnMore: some View { + private var learnMore: some View { let image = Image("icon_external_link", bundle: .dydxView) return (Text(DataLocalizer.shared?.localize(path: "APP.GENERAL.LEARN_MORE", params: nil) ?? "") + Text(" ") + Text(image)) .themeColor(foreground: .textSecondary) @@ -102,8 +109,8 @@ private struct dydxVaultView: View { .padding(.trailing, 12) } - // MARK: - Section 1 - var vaultPnlRow: some View { + // MARK: - Section 1 - PNL + private var vaultPnlRow: some View { HStack(spacing: 15) { vaultBalanceView pnlView @@ -112,7 +119,7 @@ private struct dydxVaultView: View { .padding(.horizontal, 16) } - var vaultBalanceView: some View { + private var vaultBalanceView: some View { VStack(spacing: 4) { Text(DataLocalizer.shared?.localize(path: "APP.VAULTS.YOUR_VAULT_BALANCE", params: nil) ?? "") .themeColor(foreground: .textTertiary) @@ -127,7 +134,7 @@ private struct dydxVaultView: View { .borderAndClip(style: .cornerRadius(10), borderColor: .borderDefault) } - var pnlView: some View { + private var pnlView: some View { VStack(spacing: 4) { Text(DataLocalizer.shared?.localize(path: "APP.VAULTS.YOUR_ALL_TIME_PNL", params: nil) ?? "") .themeColor(foreground: .textTertiary) @@ -150,8 +157,8 @@ private struct dydxVaultView: View { .borderAndClip(style: .cornerRadius(10), borderColor: .borderDefault) } - // MARK: - Section 2 - var aprTvlRow: some View { + // MARK: - Section 2 - APR/TVL + private var aprTvlRow: some View { HStack(spacing: 32) { aprTitleValue tvlTitleValue @@ -159,7 +166,7 @@ private struct dydxVaultView: View { .leftAligned() } - var aprTitleValue: some View { + private var aprTitleValue: some View { VStack(spacing: 4) { Text(DataLocalizer.shared?.localize(path: "APP.VAULTS.VAULT_THIRTY_DAY_APR", params: nil) ?? "") .themeColor(foreground: .textTertiary) @@ -170,7 +177,7 @@ private struct dydxVaultView: View { } } - var tvlTitleValue: some View { + private var tvlTitleValue: some View { VStack(spacing: 4) { Text(DataLocalizer.shared?.localize(path: "APP.VAULTS.TVL", params: nil) ?? "") .themeColor(foreground: .textTertiary) @@ -182,14 +189,14 @@ private struct dydxVaultView: View { } // MARK: - Section 3 - graph - var chart: some View { + private var chart: some View { viewModel.vaultChart? .createView() .frame(height: 174) } // MARK: - Section 4 - positions - var openPositionsHeader: some View { + private var openPositionsHeader: some View { HStack(spacing: 8) { Text(DataLocalizer.shared?.localize(path: "APP.TRADE.OPEN_POSITIONS", params: nil) ?? "") .themeColor(foreground: .textSecondary) @@ -217,8 +224,8 @@ private struct dydxVaultView: View { } .themeColor(background: .layer2) } - - var positionsColumnsHeader: some View { + + private var positionsColumnsHeader: some View { HStack(spacing: dydxVaultPositionViewModel.interSectionPadding) { Group { Text(DataLocalizer.shared?.localize(path: "APP.GENERAL.MARKET", params: nil) ?? "") @@ -242,8 +249,8 @@ private struct dydxVaultView: View { } .padding(.horizontal, 16) } - - var positionsList: some View { + + private var positionsList: some View { ForEach(viewModel.positions ?? [], id: \.id) { position in position.createView() .centerAligned() @@ -252,4 +259,45 @@ private struct dydxVaultView: View { } .padding(.horizontal, 16) } + + // MARK: Floating Buttons + @ViewBuilder + private var withdrawButton: some View { + if let withdrawAction = viewModel.withdrawAction { + let content = Text(localizerPathKey: "APP.GENERAL.WITHDRAW") + .themeFont(fontType: .plus, fontSize: .medium) + .themeColor(foreground: .textPrimary) + .wrappedViewModel + + PlatformButtonViewModel(content: content, + type: .defaultType(), + state: .secondary, + action: withdrawAction) + .createView() + } + } + + @ViewBuilder + private var depositButton: some View { + if let depositAction = viewModel.depositAction { + let content = Text(localizerPathKey: "APP.GENERAL.DEPOSIT") + .themeFont(fontType: .plus, fontSize: .medium) + .themeColor(foreground: .textPrimary) + .wrappedViewModel + + PlatformButtonViewModel(content: content, + type: .defaultType(), + state: .primary, + action: depositAction) + .createView() + } + } + + private var buttonStack: some View { + HStack(spacing: 12) { + withdrawButton + depositButton + } + .padding(.horizontal, 16) + } } From 00c34d7c7bed109a7c2aaffcd85f356559e67d58 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 22 Aug 2024 14:14:08 -0700 Subject: [PATCH 2/4] font size adjustment --- .../dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift index 041a3e348..2fa855b1f 100644 --- a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift +++ b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift @@ -98,7 +98,7 @@ private struct dydxVaultView: View { private var titleText: some View { Text(DataLocalizer.shared?.localize(path: "APP.VAULTS.VAULT", params: nil) ?? "") .themeColor(foreground: .textPrimary) - .themeFont(fontType: .base, fontSize: .large) + .themeFont(fontType: .plus, fontSize: .largest) } private var learnMore: some View { From 26a556a1f09c50e8e79938a257a331019f2c6fde Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 4 Sep 2024 11:25:09 -0400 Subject: [PATCH 3/4] stubbed withdraw/deposit screen --- .../dydxPresenters.xcodeproj/project.pbxproj | 12 ++ .../_Features/routing_swiftui.json | 8 + .../dydxVaultDepositWithdrawViewBuilder.swift | 95 +++++++++++ .../_v4/Vault/dydxVaultViewBuilder.swift | 6 +- .../dydxViews.xcodeproj/project.pbxproj | 20 ++- .../dydxComponents/dydxSliderInputView.swift | 1 + .../dydxTitledNumberField.swift | 35 +++- .../dydxVaultDepositWithdrawViewModel.swift | 153 ++++++++++++++++++ .../Landing/RadioButtons/RadioButtons.swift | 8 +- .../Landing/dydxVaultChartViewModel.swift | 4 + 10 files changed, 323 insertions(+), 19 deletions(-) create mode 100644 dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift create mode 100644 dydx/dydxViews/dydxViews/_v4/Vault/DepositAndWithdrawal/dydxVaultDepositWithdrawViewModel.swift diff --git a/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj b/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj index f4c5b82ee..f7c7eb298 100644 --- a/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj +++ b/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj @@ -151,6 +151,7 @@ 277E90192B1EA3C3005CCBCB /* dydxRewardsSummaryPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E90182B1EA3C3005CCBCB /* dydxRewardsSummaryPresenter.swift */; }; 277E90332B1FAE9A005CCBCB /* dydxRewardsHelpViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E90322B1FAE9A005CCBCB /* dydxRewardsHelpViewPresenter.swift */; }; 277E908B2B2118AE005CCBCB /* dydxRewardsHistoryViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E908A2B2118AE005CCBCB /* dydxRewardsHistoryViewPresenter.swift */; }; + 27823CF42C77E21A009BCD51 /* dydxVaultDepositWithdrawViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27823CF32C77E21A009BCD51 /* dydxVaultDepositWithdrawViewBuilder.swift */; }; 278A4D1E2B8EA95A003898EB /* dydxCollectFeedbackActionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278A4D1D2B8EA95A003898EB /* dydxCollectFeedbackActionBuilder.swift */; }; 278A4D932B8FA5E8003898EB /* dydxRateAppViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278A4D922B8FA5E8003898EB /* dydxRateAppViewBuilder.swift */; }; 278A4DA42B8FDD9D003898EB /* dydxRatingsWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278A4DA32B8FDD9D003898EB /* dydxRatingsWorker.swift */; }; @@ -540,6 +541,7 @@ 277E90182B1EA3C3005CCBCB /* dydxRewardsSummaryPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsSummaryPresenter.swift; sourceTree = ""; }; 277E90322B1FAE9A005CCBCB /* dydxRewardsHelpViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsHelpViewPresenter.swift; sourceTree = ""; }; 277E908A2B2118AE005CCBCB /* dydxRewardsHistoryViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsHistoryViewPresenter.swift; sourceTree = ""; }; + 27823CF32C77E21A009BCD51 /* dydxVaultDepositWithdrawViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxVaultDepositWithdrawViewBuilder.swift; sourceTree = ""; }; 278A4D1D2B8EA95A003898EB /* dydxCollectFeedbackActionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxCollectFeedbackActionBuilder.swift; sourceTree = ""; }; 278A4D922B8FA5E8003898EB /* dydxRateAppViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRateAppViewBuilder.swift; sourceTree = ""; }; 278A4DA32B8FDD9D003898EB /* dydxRatingsWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRatingsWorker.swift; sourceTree = ""; }; @@ -1423,6 +1425,7 @@ 2751D6442C59642000B36F95 /* Vault */ = { isa = PBXGroup; children = ( + 27823D022C77E30F009BCD51 /* DepositsAndWithdrawals */, 2751D6452C59643800B36F95 /* dydxVaultViewBuilder.swift */, ); path = Vault; @@ -1448,6 +1451,14 @@ path = Components; sourceTree = ""; }; + 27823D022C77E30F009BCD51 /* DepositsAndWithdrawals */ = { + isa = PBXGroup; + children = ( + 27823CF32C77E21A009BCD51 /* dydxVaultDepositWithdrawViewBuilder.swift */, + ); + path = DepositsAndWithdrawals; + sourceTree = ""; + }; 278A4D912B8FA5C1003898EB /* Rating */ = { isa = PBXGroup; children = ( @@ -2010,6 +2021,7 @@ 0243A76129BE572C00A083FE /* dydxCancelOrderActionBuilder.swift in Sources */, 0279DE482BEBE76900F9ECF8 /* dydxTargetLeverageCtaButtonViewPresenter.swift in Sources */, 023AB3B22BEACE14005230B2 /* dydxTradeInputMarginViewPresenter.swift in Sources */, + 27823CF42C77E21A009BCD51 /* dydxVaultDepositWithdrawViewBuilder.swift in Sources */, 64A4DB9929664818008D8E20 /* dydxTradeReceiptPresenter.swift in Sources */, 0236F0CB2968793A00EB995F /* dydxPortfolioFillsViewPresenter.swift in Sources */, 02A565AF2A5E310B0035469F /* dydxAlertsProvider.swift in Sources */, diff --git a/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json b/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json index 48a4283c8..e961650ab 100644 --- a/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json +++ b/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json @@ -353,6 +353,14 @@ "destination":"dydxPresenters.dydxVaultViewBuilder", "presentation":"root" }, + "/vault/deposit":{ + "destination":"dydxPresenters.dydxVaultDepositWithdrawViewBuilder", + "presentation":"prompt" + }, + "/vault/withdraw":{ + "destination":"dydxPresenters.dydxVaultDepositWithdrawViewBuilder", + "presentation":"prompt" + }, "/wallets":{ "destination":"dydxPresenters.Wallets2ViewBuilder", "presentation":"half" diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift new file mode 100644 index 000000000..2304c4866 --- /dev/null +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift @@ -0,0 +1,95 @@ +// +// dydxVaultDepositWithdrawViewBuilder.swift +// dydxPresenters +// +// Created by Michael Maguire on 8/22/24. +// + +import Utilities +import dydxViews +import PlatformParticles +import RoutingKit +import ParticlesKit +import PlatformUI +import dydxStateManager +import FloatingPanel +import PlatformRouting +import dydxFormatter + +public class dydxVaultDepositWithdrawViewBuilder: NSObject, ObjectBuilderProtocol { + public func build() -> T? { + let presenter = dydxVaultDepositWithdrawViewPresenter() + let view = presenter.viewModel?.createView() ?? PlatformViewModel().createView() + let viewController = dydxVaultDepositWithdrawViewController(presenter: presenter, view: view, configuration: .default) + return viewController as? T + } +} + +private class dydxVaultDepositWithdrawViewController: HostingViewController { + + override public func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + let presenter = presenter as? dydxVaultDepositWithdrawViewPresenterProtocol + if request?.path == "/vault/deposit" { + presenter?.transferType = .deposit + return true + } else if request?.path == "/vault/withdraw" { + presenter?.transferType = .withdraw + return true + } else { + return false + } + } +} + +private protocol dydxVaultDepositWithdrawViewPresenterProtocol: HostedViewPresenterProtocol { + var viewModel: dydxVaultDepositWithdrawViewModel? { get } + var transferType: VaultTransferType { get set } +} + +private class dydxVaultDepositWithdrawViewPresenter: HostedViewPresenter, dydxVaultDepositWithdrawViewPresenterProtocol { + var transferType: VaultTransferType = .deposit + + override init() { + let viewModel = dydxVaultDepositWithdrawViewModel(initialSelection: transferType, submitState: .disabled("placeholder")) + + super.init() + + self.viewModel = viewModel + } + + override func start() { + super.start() + + //TODO: replace with real hooks from abacus + update() + } + + //TODO: replace with real data from abacus + func update() { + var newInputReceiptChangeItems = [dydxReceiptChangeItemView]() + var newButtonReceiptChangeItems = [dydxReceiptChangeItemView]() + + newInputReceiptChangeItems.append(dydxReceiptChangeItemView(title: DataLocalizer.localize(path: "APP.VAULTS.YOUR_VAULT_BALANCE"), + value: AmountChangeModel(before: AmountTextModel(amount: 30.01), + after: AmountTextModel(amount: 30.02)))) + + newButtonReceiptChangeItems.append(.init(title: DataLocalizer.localize(path: "APP.GENERAL.CROSS_FREE_COLLATERAL"), + value: AmountChangeModel(before: AmountTextModel(amount: 30.01), + after: AmountTextModel(amount: 30.02)))) + + newButtonReceiptChangeItems.append(.init(title: DataLocalizer.localize(path: "APP.VAULTS.EST_SLIPPAGE"), + value: AmountChangeModel(before: AmountTextModel(amount: 30.01), + after: AmountTextModel(amount: 30.02)))) + + newButtonReceiptChangeItems.append(.init(title: DataLocalizer.localize(path: "APP.WITHDRAW_MODAL.EXPECTED_AMOUNT_RECEIVED"), + value: AmountChangeModel(before: AmountTextModel(amount: 30.01), + after: AmountTextModel(amount: 30.02)))) + + viewModel?.inputReceiptChangeItems = newInputReceiptChangeItems + viewModel?.buttonReceiptChangeItems = newButtonReceiptChangeItems + + viewModel?.inputInlineAlert = InlineAlertViewModel(InlineAlertViewModel.Config.init(title: "test alert", + body: "test bodytest bodytest bodytest bodytest bodytest bodytest bodytest bodytest bodytest bodytest bodytest bodytest bodytest bodytest bodytest bodytest body", + level: .error)) + } +} diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Vault/dydxVaultViewBuilder.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/dydxVaultViewBuilder.swift index 5450c45b2..0627678ff 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Vault/dydxVaultViewBuilder.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/dydxVaultViewBuilder.swift @@ -49,12 +49,12 @@ private class dydxVaultViewBuilderPresenter: HostedViewPresenter 0 { - self?.viewModel?.withdrawAction = {} + self?.viewModel?.depositAction = { Router.shared?.navigate(to: RoutingRequest(path: "/vault/withdraw"), animated: true, completion: nil) } } } .store(in: &subscriptions) - viewModel?.depositAction = {} - + viewModel?.depositAction = { Router.shared?.navigate(to: RoutingRequest(path: "/vault/deposit"), animated: true, completion: nil) } + //TODO: remove & replace, test only Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] _ in guard let self = self else { return } diff --git a/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj b/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj index 8abc78b29..804fc6b9a 100644 --- a/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj +++ b/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj @@ -192,6 +192,7 @@ 277E91082B2241C1005CCBCB /* dydxRewardsRewardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E91072B2241C1005CCBCB /* dydxRewardsRewardView.swift */; }; 277E914A2B23BB74005CCBCB /* dydxRewardsLearnMoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E91492B23BB74005CCBCB /* dydxRewardsLearnMoreView.swift */; }; 277E918B2B27762F005CCBCB /* dydxRewardsLaunchIncentivesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E918A2B27762F005CCBCB /* dydxRewardsLaunchIncentivesView.swift */; }; + 27823D132C77E38C009BCD51 /* dydxVaultDepositWithdrawViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27823D122C77E38C009BCD51 /* dydxVaultDepositWithdrawViewModel.swift */; }; 278A4DA22B8FA609003898EB /* dydxRateAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278A4DA12B8FA609003898EB /* dydxRateAppView.swift */; }; 27A799B92A66EC2D007C3D04 /* ThemeClassicDark.json in Resources */ = {isa = PBXBuildFile; fileRef = 27A799B82A66EC2D007C3D04 /* ThemeClassicDark.json */; }; 27AAA9862ACE34C800AF3C56 /* SwiftMessages+Banner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27AAA9852ACE34C800AF3C56 /* SwiftMessages+Banner.swift */; }; @@ -573,6 +574,7 @@ 277E91072B2241C1005CCBCB /* dydxRewardsRewardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsRewardView.swift; sourceTree = ""; }; 277E91492B23BB74005CCBCB /* dydxRewardsLearnMoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsLearnMoreView.swift; sourceTree = ""; }; 277E918A2B27762F005CCBCB /* dydxRewardsLaunchIncentivesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsLaunchIncentivesView.swift; sourceTree = ""; }; + 27823D122C77E38C009BCD51 /* dydxVaultDepositWithdrawViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxVaultDepositWithdrawViewModel.swift; sourceTree = ""; }; 278A4DA12B8FA609003898EB /* dydxRateAppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRateAppView.swift; sourceTree = ""; }; 27A799B82A66EC2D007C3D04 /* ThemeClassicDark.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ThemeClassicDark.json; sourceTree = ""; }; 27AAA9852ACE34C800AF3C56 /* SwiftMessages+Banner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftMessages+Banner.swift"; sourceTree = ""; }; @@ -1493,7 +1495,7 @@ 2751D6472C59645000B36F95 /* Vault */ = { isa = PBXGroup; children = ( - 2751D6772C597BFC00B36F95 /* DepositsAndWithdrawals */, + 27823D112C77E370009BCD51 /* DepositAndWithdrawal */, 2751D6762C597BEC00B36F95 /* Landing */, ); path = Vault; @@ -1510,13 +1512,6 @@ path = Landing; sourceTree = ""; }; - 2751D6772C597BFC00B36F95 /* DepositsAndWithdrawals */ = { - isa = PBXGroup; - children = ( - ); - path = DepositsAndWithdrawals; - sourceTree = ""; - }; 277E8FF82B1EA083005CCBCB /* TradingRewards */ = { isa = PBXGroup; children = ( @@ -1541,6 +1536,14 @@ path = Components; sourceTree = ""; }; + 27823D112C77E370009BCD51 /* DepositAndWithdrawal */ = { + isa = PBXGroup; + children = ( + 27823D122C77E38C009BCD51 /* dydxVaultDepositWithdrawViewModel.swift */, + ); + path = DepositAndWithdrawal; + sourceTree = ""; + }; 278A4D942B8FA5F5003898EB /* Rating */ = { isa = PBXGroup; children = ( @@ -2059,6 +2062,7 @@ 0253155129BFA62700D6CC9B /* dydxOnboardScanInstructionsView.swift in Sources */, 27CDA3D42BBF1AD700FEAFFE /* dydxMultipleOrdersExistViewModel.swift in Sources */, 024B7B5C28B7F90100F7C386 /* dydxViewBundleClass.swift in Sources */, + 27823D132C77E38C009BCD51 /* dydxVaultDepositWithdrawViewModel.swift in Sources */, 02A9B60C29005A3F00AE1516 /* AmountChange.swift in Sources */, 64A4DB5329662070008D8E20 /* dydxTradeInputSizeView.swift in Sources */, 02E318A82AB231FA0074DA98 /* PlaceholderView.swift in Sources */, diff --git a/dydx/dydxViews/dydxViews/Shared/dydxComponents/dydxSliderInputView.swift b/dydx/dydxViews/dydxViews/Shared/dydxComponents/dydxSliderInputView.swift index 0a3344d06..d0e5f41c6 100644 --- a/dydx/dydxViews/dydxViews/Shared/dydxComponents/dydxSliderInputView.swift +++ b/dydx/dydxViews/dydxViews/Shared/dydxComponents/dydxSliderInputView.swift @@ -54,6 +54,7 @@ private struct dydxSliderTextInput: View { numberFormatter: viewModel.numberFormatter, minValue: viewModel.minValue, maxValue: viewModel.maxValue, + isMaxButtonVisible: false, value: $viewModel.value) } diff --git a/dydx/dydxViews/dydxViews/Shared/dydxComponents/dydxTitledNumberField.swift b/dydx/dydxViews/dydxViews/Shared/dydxComponents/dydxTitledNumberField.swift index 9398b9b78..87368c3b1 100644 --- a/dydx/dydxViews/dydxViews/Shared/dydxComponents/dydxTitledNumberField.swift +++ b/dydx/dydxViews/dydxViews/Shared/dydxComponents/dydxTitledNumberField.swift @@ -8,6 +8,7 @@ import SwiftUI import dydxFormatter import PlatformUI +import Utilities /// Effectively a TextField which forces its input as a number /// Supports dydx-style title and title accesory view @@ -17,6 +18,7 @@ struct dydxTitledNumberField: View { let numberFormatter: dydxNumberInputFormatter let minValue: Double let maxValue: Double + let isMaxButtonVisible: Bool @Binding var value: Double? @State private var textWidth: CGFloat = 0 @@ -60,17 +62,36 @@ struct dydxTitledNumberField: View { .truncationMode(.middle) .frame(width: textWidth) } + + private var maxButton: some View { + let buttonContent = Text(DataLocalizer.localize(path: "APP.GENERAL.MAX")) + .themeFont(fontSize: .small) + .wrappedViewModel + + return PlatformButtonViewModel(content: buttonContent, type: .pill, state: .secondary, action: { + PlatformView.hideKeyboard() + self.value = maxValue + }) + .createView() + + } var body: some View { - VStack(alignment: .leading, spacing: 5) { - HStack(spacing: 5) { - titleView - accessoryTitleView + HStack(spacing: 0) { + VStack(alignment: .leading, spacing: 5) { + HStack(spacing: 5) { + titleView + accessoryTitleView + } + textFieldView + } + if isMaxButtonVisible { + Spacer() + maxButton } - textFieldView } - .padding(.vertical, 8) - .padding(.horizontal, 12) + .padding(.vertical, 12) + .padding(.horizontal, 16) .makeInput() } } diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/DepositAndWithdrawal/dydxVaultDepositWithdrawViewModel.swift b/dydx/dydxViews/dydxViews/_v4/Vault/DepositAndWithdrawal/dydxVaultDepositWithdrawViewModel.swift new file mode 100644 index 000000000..bb15087a8 --- /dev/null +++ b/dydx/dydxViews/dydxViews/_v4/Vault/DepositAndWithdrawal/dydxVaultDepositWithdrawViewModel.swift @@ -0,0 +1,153 @@ +// +// dydxVaultDepositWithdrawViewModel.swift +// dydxViews +// +// Created by Michael Maguire on 8/22/24. +// + +import SwiftUI +import PlatformUI +import Utilities +import dydxFormatter + +public class dydxVaultDepositWithdrawViewModel: PlatformViewModel { + public enum State { + case enabled(String) + case disabled(String) + } + + public let submitState: State + public var submitAction: (() -> Void)? + + @Published public private(set) var numberFormatter = dydxNumberInputFormatter() + + @Published fileprivate var selected: VaultTransferType + @Published fileprivate var amount: Double? + fileprivate var maxAmount: Double = 0 + + public var inputReceiptChangeItems: [dydxReceiptChangeItemView]? + public var inputInlineAlert: InlineAlertViewModel? + public var buttonReceiptChangeItems: [dydxReceiptChangeItemView]? + + public init(initialSelection: VaultTransferType, submitState: State) { + self.selected = initialSelection + self.submitState = submitState + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + return VaultDepositWithdrawView(viewModel: self) + .wrappedInAnyView() + } + } +} + +public enum VaultTransferType: CaseIterable, RadioButtonContentDisplayable { + case deposit + case withdraw + + var displayText: String { + switch self { + case .deposit: return DataLocalizer.localize(path: "APP.GENERAL.DEPOSIT") + case .withdraw: return DataLocalizer.localize(path: "APP.GENERAL.WITHDRAW") + } + } + + public var inputFieldTitle: String { + switch self { + case .deposit: return DataLocalizer.localize(path: "APP.VAULTS.ENTER_AMOUNT_TO_DEPOSIT") + case .withdraw: return DataLocalizer.localize(path: "APP.VAULTS.ENTER_AMOUNT_TO_WITHDRAW") + } + } +} + +fileprivate struct VaultDepositWithdrawView: View { + @ObservedObject var viewModel: dydxVaultDepositWithdrawViewModel + + var options = VaultTransferType.allCases + + private var radioButtonSelector: some View { + RadioButtonGroup(selected: $viewModel.selected, + options: options, + buttonClipStyle: .capsule, + fontType: .plus, + fontSize: .larger, + itemWidth: nil, + itemHeight: 44) + .leftAligned() + } + + var body: some View { + + VStack(spacing: 0) { + radioButtonSelector + Color.clear.frame(height: 24) + ScrollView(showsIndicators: false) { + VStack(spacing: 18) { + inputArea + } + } + Spacer(minLength: 18) + buttonArea + } + .padding(.horizontal, 16) + .padding(.top, 48) + .padding(.bottom, self.safeAreaInsets?.bottom) + .makeSheet() + .frame(maxWidth: .infinity) + .themeColor(background: .layer3) + .ignoresSafeArea(edges: [.bottom]) + } + + private var inputArea: some View { + VStack(spacing: 16) { + dydxTitledNumberField(title: viewModel.selected.inputFieldTitle, + accessoryTitle: nil, + numberFormatter: viewModel.numberFormatter, + minValue: 0, + maxValue: viewModel.maxAmount, + isMaxButtonVisible: true, + value: $viewModel.amount) + VStack(spacing: 8) { + ForEach(self.viewModel.inputReceiptChangeItems ?? [], id: \.id) { $0.createView() } + } + .padding(.horizontal, 16) + } + .padding(.bottom, 16) + .themeColor(background: .layer2) + .clipShape(.rect(cornerRadius: 10)) + } + + private var submitButton: some View { + let content: Text + let state: PlatformButtonState + switch viewModel.submitState { + case .enabled(let text): + state = .primary + content = Text(text) + .themeColor(foreground: .textPrimary) + .themeFont(fontType: .base, fontSize: .large) + case .disabled(let text): + state = .disabled + content = Text(text) + .themeColor(foreground: .textTertiary) + .themeFont(fontType: .base, fontSize: .large) + } + return PlatformButtonViewModel(content: content.wrappedViewModel, state: state, action: viewModel.submitAction ?? {}) + .createView() + } + + private var buttonArea: some View { + VStack(spacing: 16) { + VStack(spacing: 8) { + ForEach(self.viewModel.buttonReceiptChangeItems ?? [], id: \.id) { $0.createView() } + } + .padding(.horizontal, 16) + submitButton + } + .padding(.top, 16) + .themeColor(background: .layer2) + .clipShape(.rect(cornerRadius: 10)) + } +} diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift index fdb481020..e34105fb3 100644 --- a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift +++ b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift @@ -22,6 +22,8 @@ struct RadioButtonGroup: View { let options: [ButtonItem] let buttonClipStyle: ClipStyle + let fontType: ThemeFont.FontType + let fontSize: ThemeFont.FontSize /// when not specified, width will be natural. When specified, width will be forced let itemWidth: CGFloat? /// when not specified, height will be natural. When specified, height will be forced @@ -34,6 +36,8 @@ struct RadioButtonGroup: View { RadioButton(displayText: option.displayText, isSelected: selected == option, clipStyle: buttonClipStyle, + fontType: fontType, + fontSize: fontSize, width: itemWidth, height: itemHeight ) { @@ -48,6 +52,8 @@ struct RadioButton: View { let displayText: String let isSelected: Bool let clipStyle: ClipStyle + let fontType: ThemeFont.FontType + let fontSize: ThemeFont.FontSize let width: CGFloat? let height: CGFloat? let selectionAction: () -> Void @@ -64,7 +70,7 @@ struct RadioButton: View { Text(displayText) .lineLimit(1) .themeColor(foreground: isSelected ? .textPrimary : .textTertiary) - .themeFont(fontType: .base, fontSize: .smaller) + .themeFont(fontType: fontType, fontSize: fontSize) // if width is specified, i.e. non-nil, setting horizontal inset to 0 will allow entire space to be used horizontally .padding(.horizontal, width == nil ? 8 : 0) // if height is specified, i.e. non-nil, setting vertical inset to 0 will allow entire space to be used horizontally diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift index 448448b22..c63873684 100644 --- a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift +++ b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift @@ -145,6 +145,8 @@ private struct dydxVaultChartView: View { RadioButtonGroup(selected: $viewModel.selectedValueType, options: viewModel.valueTypeOptions, buttonClipStyle: .capsule, + fontType: .base, + fontSize: .smaller, itemWidth: nil, itemHeight: 40 ) @@ -152,6 +154,8 @@ private struct dydxVaultChartView: View { RadioButtonGroup(selected: $viewModel.selectedValueTime, options: viewModel.valueTimeOptions, buttonClipStyle: .circle, + fontType: .base, + fontSize: .smaller, itemWidth: 40, itemHeight: 40 ) From cd9fb84689edb933ea15562fade240ad0bcd707a Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 6 Sep 2024 00:52:59 -0400 Subject: [PATCH 4/4] enabled/disabled button states/texts --- .../dydxVaultDepositWithdrawViewBuilder.swift | 4 +- .../dydxVaultDepositWithdrawViewModel.swift | 39 +++++++++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift index 2304c4866..54b58b084 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift @@ -50,7 +50,7 @@ private class dydxVaultDepositWithdrawViewPresenter: HostedViewPresenter