diff --git a/BlackjackSim/BlackjackSim.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/BlackjackSim/BlackjackSim.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/BlackjackSim/BlackjackSim.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/PlatformUI/PlatformUI.xcodeproj/project.pbxproj b/PlatformUI/PlatformUI.xcodeproj/project.pbxproj index bea8ca59c..56a42e61b 100644 --- a/PlatformUI/PlatformUI.xcodeproj/project.pbxproj +++ b/PlatformUI/PlatformUI.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 27044F882BBB2ADF004C750D /* Text+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27044F872BBB2ADF004C750D /* Text+Ext.swift */; }; 274C47EA2C0FC6CF000212C3 /* EdgeInsets+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C47E92C0FC6CF000212C3 /* EdgeInsets+Ext.swift */; }; 276581F82C139F36009E072A /* SingleAxisGeometryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 276581F72C139F36009E072A /* SingleAxisGeometryReader.swift */; }; + 27673BBE2C8FD24400689E3F /* AttributedString+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27673BBD2C8FD24400689E3F /* AttributedString+Ext.swift */; }; 277E7AC62BBF3BE8009F95DE /* InlineAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E7AC52BBF3BE8009F95DE /* InlineAlert.swift */; }; 27E6A7322AB8D5F600026CB5 /* SwipeActionsViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E6A7312AB8D5F600026CB5 /* SwipeActionsViewModifier.swift */; }; 6488BBDC296F6AEA0096502F /* TabItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6488BBDB296F6AEA0096502F /* TabItemViewModel.swift */; }; @@ -139,6 +140,7 @@ 27044F872BBB2ADF004C750D /* Text+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Text+Ext.swift"; sourceTree = ""; }; 274C47E92C0FC6CF000212C3 /* EdgeInsets+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EdgeInsets+Ext.swift"; sourceTree = ""; }; 276581F72C139F36009E072A /* SingleAxisGeometryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleAxisGeometryReader.swift; sourceTree = ""; }; + 27673BBD2C8FD24400689E3F /* AttributedString+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Ext.swift"; sourceTree = ""; }; 277E7AC52BBF3BE8009F95DE /* InlineAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineAlert.swift; sourceTree = ""; }; 27E6A7312AB8D5F600026CB5 /* SwipeActionsViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeActionsViewModifier.swift; sourceTree = ""; }; 366BD14FE1ED4F2AF21D924E /* Pods-iOS-PlatformUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformUI.release.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformUI/Pods-iOS-PlatformUI.release.xcconfig"; sourceTree = ""; }; @@ -381,6 +383,7 @@ children = ( 27044F872BBB2ADF004C750D /* Text+Ext.swift */, 274C47E92C0FC6CF000212C3 /* EdgeInsets+Ext.swift */, + 27673BBD2C8FD24400689E3F /* AttributedString+Ext.swift */, ); path = Extensions; sourceTree = ""; @@ -664,6 +667,7 @@ 02DE88F828A2DB9C00728FF3 /* PlatformUIBundleClass.swift in Sources */, 02F16FD928B47E2D0085DC58 /* PlatformTableViewCell.swift in Sources */, 024B794528B6A6A200F7C386 /* PlatformIcon.swift in Sources */, + 27673BBE2C8FD24400689E3F /* AttributedString+Ext.swift in Sources */, 0230377828C15C0E00412B72 /* PlatformViewModel.swift in Sources */, 276581F82C139F36009E072A /* SingleAxisGeometryReader.swift in Sources */, 0278DD0E2A7C7A1400FE6ABE /* PlatformViewModel+Ext.swift in Sources */, diff --git a/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift b/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift index a0b22a407..940b2597c 100644 --- a/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift +++ b/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift @@ -67,6 +67,9 @@ public class PlatformButtonViewModel: PlatformVie switch self.state { case .primary: button + // adding invisible border ensures different states have equal heights/widths, + // otherwise, no border buttons are slightly shorter than border buttons + .border(borderWidth: borderWidth, cornerRadius: cornerRadius, borderColor: ThemeColor.SemanticColor.transparent.color) case .destructive: button .border(borderWidth: borderWidth, cornerRadius: cornerRadius, borderColor: ThemeColor.SemanticColor.colorFadedRed.color) diff --git a/PlatformUI/PlatformUI/Components/Extensions/AttributedString+Ext.swift b/PlatformUI/PlatformUI/Components/Extensions/AttributedString+Ext.swift new file mode 100644 index 000000000..230217111 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Extensions/AttributedString+Ext.swift @@ -0,0 +1,42 @@ +// +// AttributedString+Ext.swift +// PlatformUI +// +// Created by Michael Maguire on 9/9/24. +// + +import Utilities + +public extension AttributedString { + init(text: String, + // localizerPathKeys mapped to the action to be performed when tapped + hyperlinks: [String: String], + foreground: ThemeColor.SemanticColor = .textPrimary + ) { + var text = text + for (key, url) in hyperlinks { + let hyperlinkText = DataLocalizer.localize(path: key) + // markdown supported as of iOS 15! + text = text.replacingOccurrences(of: hyperlinkText, with: "[\(hyperlinkText)](\(url))") + } + var attributedString = (try? AttributedString(markdown: text)) ?? AttributedString(text) + .themeColor(foreground: foreground) + for run in attributedString.runs { + if let _ = run.link { + attributedString[run.range].underlineStyle = .single + attributedString[run.range].foregroundColor = foreground.color // Change to any desired color + } + } + self = attributedString + } + + init(localizerPathKey: String, + params: [String: String]? = nil, + // localizerPathKeys mapped to the action to be performed when tapped + hyperlinks: [String: String], + foreground: ThemeColor.SemanticColor = .textPrimary + ) { + var localizedText = DataLocalizer.localize(path: localizerPathKey, params: params) + self.init(text: localizedText, hyperlinks: hyperlinks, foreground: foreground) + } +} diff --git a/PlatformUI/PlatformUI/Components/Labels/InlineAlert.swift b/PlatformUI/PlatformUI/Components/Labels/InlineAlert.swift index e0541b770..1e493271c 100644 --- a/PlatformUI/PlatformUI/Components/Labels/InlineAlert.swift +++ b/PlatformUI/PlatformUI/Components/Labels/InlineAlert.swift @@ -24,7 +24,6 @@ public class InlineAlertViewModel: PlatformViewModel { private var title: AnyView? { guard let titleText = config.title else { return nil } return Text(titleText) - .themeColor(foreground: .textPrimary) .themeFont(fontType: .plus, fontSize: .medium) .wrappedInAnyView() } @@ -32,7 +31,6 @@ public class InlineAlertViewModel: PlatformViewModel { private var body: AnyView? { guard let bodyText = config.body else { return nil } return Text(bodyText) - .themeColor(foreground: .textPrimary) .themeFont(fontType: .base, fontSize: .small) .wrappedInAnyView() } @@ -66,11 +64,21 @@ public class InlineAlertViewModel: PlatformViewModel { public extension InlineAlertViewModel { struct Config { - public var title: String? - public var body: String? + public var title: AttributedString? = nil + public var body: AttributedString? = nil public var level: Level public init(title: String?, body: String?, level: Level) { + if let title = title { + self.title = AttributedString(title) + } + if let body = body { + self.body = AttributedString(body) + } + self.level = level + } + + public init(title: AttributedString?, body: AttributedString?, level: Level) { self.title = title self.body = body self.level = level diff --git a/dydx/dydx.xcworkspace/xcshareddata/swiftpm/Package.resolved b/dydx/dydx.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1601797ea..30adbe9ee 100644 --- a/dydx/dydx.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/dydx/dydx.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "975d00e29efb8d2ca017c5e61df90418ac01f7d7143e85a3f9ddb4eb982154e4", + "originHash" : "3cd7346cace16cf660f9c22302c5ca6770335c67902f3fd155011356a9d43929", "pins" : [ { "identity" : "bigint", @@ -37,15 +37,6 @@ "version" : "2.0.2" } }, - { - "identity" : "keyboardobserving", - "kind" : "remoteSourceControl", - "location" : "https://github.com/nickffox/KeyboardObserving", - "state" : { - "branch" : "master", - "revision" : "48134b5460435cc9d048223ad7560ee2e40f3d4a" - } - }, { "identity" : "percy-xcui-swift", "kind" : "remoteSourceControl", @@ -199,15 +190,6 @@ "version" : "9.0.9" } }, - { - "identity" : "swiftui-introspect", - "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/SwiftUI-Introspect.git", - "state" : { - "revision" : "121c146fe591b1320238d054ae35c81ffa45f45a", - "version" : "0.12.0" - } - }, { "identity" : "wallet-mobile-sdk", "kind" : "remoteSourceControl", diff --git a/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj b/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj index f7c7eb298..a763ff5e1 100644 --- a/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj +++ b/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj @@ -139,6 +139,7 @@ 2741B25C2C06A0E100693B17 /* dydxAnalytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2741B2572C06A0CC00693B17 /* dydxAnalytics.framework */; }; 2741E3702A68787A000FA190 /* settings_direction_color_preference.json in Resources */ = {isa = PBXBuildFile; fileRef = 2741E3632A68787A000FA190 /* settings_direction_color_preference.json */; }; 2741E3732A689740000FA190 /* dydxDirectionColorPreferenceViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2741E3722A689740000FA190 /* dydxDirectionColorPreferenceViewBuilder.swift */; }; + 27457F5B2C8AC8B700873640 /* dydxVaultDepositWithdrawConfirmationViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27457F5A2C8AC8B700873640 /* dydxVaultDepositWithdrawConfirmationViewBuilder.swift */; }; 2749F8FF2B853B8700D6BA16 /* dydxRewardsLaunchIncentivesPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2749F8FE2B853B8700D6BA16 /* dydxRewardsLaunchIncentivesPresenter.swift */; }; 2751D6432C59614C00B36F95 /* tabs_v4_vault.json in Resources */ = {isa = PBXBuildFile; fileRef = 2751D6352C59614C00B36F95 /* tabs_v4_vault.json */; }; 2751D6462C59643800B36F95 /* dydxVaultViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2751D6452C59643800B36F95 /* dydxVaultViewBuilder.swift */; }; @@ -529,6 +530,7 @@ 2741E3632A68787A000FA190 /* settings_direction_color_preference.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = settings_direction_color_preference.json; sourceTree = ""; }; 2741E3722A689740000FA190 /* dydxDirectionColorPreferenceViewBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxDirectionColorPreferenceViewBuilder.swift; sourceTree = ""; }; 2742C05D2BF6898500E13C09 /* dydxAnalytics.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = dydxAnalytics.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 27457F5A2C8AC8B700873640 /* dydxVaultDepositWithdrawConfirmationViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxVaultDepositWithdrawConfirmationViewBuilder.swift; sourceTree = ""; }; 2749F8FE2B853B8700D6BA16 /* dydxRewardsLaunchIncentivesPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxRewardsLaunchIncentivesPresenter.swift; sourceTree = ""; }; 2751D6352C59614C00B36F95 /* tabs_v4_vault.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tabs_v4_vault.json; sourceTree = ""; }; 2751D6452C59643800B36F95 /* dydxVaultViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxVaultViewBuilder.swift; sourceTree = ""; }; @@ -1422,11 +1424,19 @@ path = DirectionColorPreference; sourceTree = ""; }; + 27457F4C2C8AC89800873640 /* Landing */ = { + isa = PBXGroup; + children = ( + 2751D6452C59643800B36F95 /* dydxVaultViewBuilder.swift */, + ); + path = Landing; + sourceTree = ""; + }; 2751D6442C59642000B36F95 /* Vault */ = { isa = PBXGroup; children = ( + 27457F4C2C8AC89800873640 /* Landing */, 27823D022C77E30F009BCD51 /* DepositsAndWithdrawals */, - 2751D6452C59643800B36F95 /* dydxVaultViewBuilder.swift */, ); path = Vault; sourceTree = ""; @@ -1455,6 +1465,7 @@ isa = PBXGroup; children = ( 27823CF32C77E21A009BCD51 /* dydxVaultDepositWithdrawViewBuilder.swift */, + 27457F5A2C8AC8B700873640 /* dydxVaultDepositWithdrawConfirmationViewBuilder.swift */, ); path = DepositsAndWithdrawals; sourceTree = ""; @@ -2032,6 +2043,7 @@ 02FAFA5C29D4E08E001A0903 /* dydxDebugViewBuilder.swift in Sources */, 02F543B22C17ABBB000924E4 /* dydxGasTokenViewBuilder.swift in Sources */, 02F700FE29EA0FD9004DEB5E /* dydxReceiptPresenter.swift in Sources */, + 27457F5B2C8AC8B700873640 /* dydxVaultDepositWithdrawConfirmationViewBuilder.swift in Sources */, 027E1EF829CA27CD0098666F /* dydxSettingsLandingViewBuilder.swift in Sources */, 020DBF1E29E092C00068AAA6 /* dydxTransferDepositViewPresenter.swift in Sources */, 277987512BA33F15006DC5CD /* dydxSelectedMarketStore.swift in Sources */, diff --git a/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json b/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json index e961650ab..215199aff 100644 --- a/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json +++ b/dydx/dydxPresenters/dydxPresenters/_Features/routing_swiftui.json @@ -361,6 +361,14 @@ "destination":"dydxPresenters.dydxVaultDepositWithdrawViewBuilder", "presentation":"prompt" }, + "/vault/deposit_confirm":{ + "destination":"dydxPresenters.dydxVaultDepositWithdrawConfirmationViewBuilder", + "presentation":"push" + }, + "/vault/withdraw_confirm":{ + "destination":"dydxPresenters.dydxVaultDepositWithdrawConfirmationViewBuilder", + "presentation":"push" + }, "/wallets":{ "destination":"dydxPresenters.Wallets2ViewBuilder", "presentation":"half" diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawConfirmationViewBuilder.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawConfirmationViewBuilder.swift new file mode 100644 index 000000000..e93f52b9b --- /dev/null +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawConfirmationViewBuilder.swift @@ -0,0 +1,105 @@ +// +// dydxVaultDepositWithdrawConfirmationViewBuilder.swift +// dydxPresenters +// +// Created by Michael Maguire on 9/6/24. +// + +import Utilities +import dydxViews +import PlatformParticles +import RoutingKit +import ParticlesKit +import PlatformUI +import dydxStateManager +import FloatingPanel +import PlatformRouting +import dydxFormatter + +public class dydxVaultDepositWithdrawConfirmationViewBuilder: NSObject, ObjectBuilderProtocol { + public func build() -> T? { + let presenter = dydxVaultDepositWithdrawConfirmationViewPresenter() + let view = presenter.viewModel?.createView() ?? PlatformViewModel().createView() + let viewController = dydxVaultDepositWithdrawConfirmationViewController(presenter: presenter, view: view, configuration: .default) + return viewController as? T + } +} + +private class dydxVaultDepositWithdrawConfirmationViewController: HostingViewController { + + override public func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + let presenter = presenter as? dydxVaultDepositWithdrawConfirmationViewPresenterProtocol + if request?.path == "/vault/deposit_confirm" { + presenter?.transferType = .deposit + return true + } else if request?.path == "/vault/withdraw_confirm" { + presenter?.transferType = .withdraw + return true + } else { + return false + } + } +} + +private protocol dydxVaultDepositWithdrawConfirmationViewPresenterProtocol: HostedViewPresenterProtocol { + var viewModel: dydxVaultDepositWithdrawConfirmationViewModel? { get } + var transferType: VaultTransferType { get set } +} + +private class dydxVaultDepositWithdrawConfirmationViewPresenter: HostedViewPresenter, dydxVaultDepositWithdrawConfirmationViewPresenterProtocol { + var transferType: VaultTransferType = .deposit { + didSet { + viewModel?.transferType = transferType + } + } + + override init() { + + super.init() + + let viewModel = dydxVaultDepositWithdrawConfirmationViewModel(transferType: transferType) + + viewModel.cancelAction = { + Router.shared?.navigate(to: RoutingRequest(path: "/action/dismiss"), animated: true, completion: nil) + } + + //TODO: replace + viewModel.elevatedSlippageAmount = 4.20 + viewModel.requiresAcknowledgeHighSlippage = true + + 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() { + let crossFreeCollateralReceiptItem = dydxReceiptChangeItemView(title: DataLocalizer.localize(path: "APP.GENERAL.CROSS_FREE_COLLATERAL"), + value: AmountChangeModel(before: AmountTextModel(amount: 30.01), + after: AmountTextModel(amount: 30.02))) + let yourVaultBalanceReceiptItem = dydxReceiptChangeItemView(title: DataLocalizer.localize(path: "APP.VAULTS.YOUR_VAULT_BALANCE"), + value: AmountChangeModel(before: AmountTextModel(amount: 30.01), + after: AmountTextModel(amount: 30.02))) + let estSlippageReceiptItem = dydxReceiptChangeItemView(title: DataLocalizer.localize(path: "APP.VAULTS.EST_SLIPPAGE"), + value: AmountChangeModel(before: AmountTextModel(amount: 30.01), + after: AmountTextModel(amount: 30.02))) + let expectedAmountReceivedItem = dydxReceiptChangeItemView(title: DataLocalizer.localize(path: "APP.WITHDRAW_MODAL.EXPECTED_AMOUNT_RECEIVED"), + value: AmountChangeModel(before: AmountTextModel(amount: 30.01), + after: AmountTextModel(amount: 30.02))) + let crossMarginUsageItem = dydxReceiptChangeItemView(title: DataLocalizer.localize(path: "APP.GENERAL.CROSS_MARGIN_USAGE"), + value: AmountChangeModel(before: AmountTextModel(amount: 30.01), + after: AmountTextModel(amount: 30.02))) + + switch transferType { + case .deposit: + viewModel?.receiptItems = [crossFreeCollateralReceiptItem, crossMarginUsageItem, yourVaultBalanceReceiptItem] + case .withdraw: + viewModel?.receiptItems = [crossFreeCollateralReceiptItem, yourVaultBalanceReceiptItem, estSlippageReceiptItem, expectedAmountReceivedItem] + } + } +} diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift index 54b58b084..233410bd6 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Vault/DepositsAndWithdrawals/dydxVaultDepositWithdrawViewBuilder.swift @@ -50,10 +50,19 @@ private class dydxVaultDepositWithdrawViewPresenter: HostedViewPresenter Void)? + public var cancelAction: (() -> Void)? + public var elevatedSlippageAmount: Double? + public var receiptItems: [dydxReceiptChangeItemView]? + + @Published public var transferType: VaultTransferType + @Published public var requiresAcknowledgeHighSlippage: Bool = false + + @Published fileprivate var amount: Double? + @Published fileprivate var hasAcknowledgedHighSlippage: Bool = false + + public init(transferType: VaultTransferType) { + self.transferType = transferType + + + } + + 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 VaultDepositWithdrawConfirmationView(viewModel: self) + .wrappedInAnyView() + } + } +} + +fileprivate struct VaultDepositWithdrawConfirmationView: View { + @ObservedObject var viewModel: dydxVaultDepositWithdrawConfirmationViewModel + + var options = VaultTransferType.allCases + + var body: some View { + + VStack(spacing: 24) { + titleRow + transferVisualization + Spacer() + receipts + withdrawalSlippageInlineAlert + checkboxRow + submitButton + } + .padding(.horizontal, 16) + .padding(.top, 48) + .padding(.bottom, self.safeAreaInsets?.bottom) + .makeSheet() + .frame(maxWidth: .infinity) + .themeColor(background: .layer3) + .ignoresSafeArea(edges: [.bottom]) + } + + private var titleRow: some View { + HStack(spacing: 16) { + backButton + titleText + Spacer() + } + .padding(.horizontal, 8) + } + + private var titleText: some View { + Text(viewModel.transferType.confirmTransferText) + .themeColor(foreground: .textPrimary) + .themeFont(fontType: .plus, fontSize: .largest) + } + + private var backButton: some View { + ChevronBackButtonModel(onBackButtonTap: viewModel.cancelAction ?? {}) + .createView() + } + + private var transferVisualization: some View { + return HStack(alignment: .center, spacing: 16) { + generationTransferStep(title: viewModel.transferType.transferOriginTitleText, thumbnail: viewModel.transferType.transferOriginImage, subtitle: "placeholder") + Image(systemName: "chevron.forward.dotted.chevron.forward") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 16, height: 16) + .themeColor(foreground: .textTertiary) + generationTransferStep(title: viewModel.transferType.transferDestinationTitleText, thumbnail: viewModel.transferType.transferDestinationImage, subtitle: viewModel.transferType.transferDestinationSubtitleText) + } + .fixedSize(horizontal: false, vertical: true) + + } + + + private func generationTransferStep(title: String, thumbnail: Image?, subtitle: String) -> some View { + VStack(spacing: 8) { + Text(title) + .themeColor(foreground: .textTertiary) + .themeFont(fontType: .base, fontSize: .small) + .lineLimit(1) + .minimumScaleFactor(0.5) + VStack(spacing: 8) { + if let thumbnail { + thumbnail + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 32, height: 32) + } else { + let thumbnailText = subtitle.prefix(1) + Text(thumbnailText) + .frame(width: 32, height: 32) + .themeColor(foreground: .textTertiary) + .themeColor(background: .layer1) + .clipShape(.circle) + } + Text(subtitle) + .themeColor(foreground: .textSecondary) + .themeFont(fontType: .base, fontSize: .medium) + .lineLimit(1) + .minimumScaleFactor(0.5) + } + .centerAligned() + .padding(.all, 16) + .themeColor(background: .layer2) + .clipShape(.rect(cornerRadius: 10)) + } + } + + @ViewBuilder + private var receipts: some View { + if viewModel.receiptItems?.count ?? 0 > 0 { + VStack(spacing: 8) { + ForEach(viewModel.receiptItems ?? [], id: \.id) { $0.createView() } + } + .padding(.all, 16) + .themeColor(background: .layer2) + .clipShape(.rect(cornerRadius: 10)) + } + } + + @ViewBuilder + private var withdrawalSlippageInlineAlert: some View { + if let elevatedSlippageAmount = viewModel.elevatedSlippageAmount { + let withdrawalSlippageInlineAlertAttributedText = AttributedString(localizerPathKey: "APP.VAULTS.SLIPPAGE_WARNING", + params: ["LINK": DataLocalizer.localize(path: "APP.VAULTS.VAULT_FAQS"), + "AMOUNT": dydxFormatter.shared.percent(number: elevatedSlippageAmount, digits: 2) ?? "--"], + //TODO: Replace + hyperlinks: ["withdraw": "https://test.com", + "APP.VAULTS.VAULT_FAQS": "https://purple.com"], + foreground: .textPrimary) + InlineAlertViewModel(.init(title: nil, + body: withdrawalSlippageInlineAlertAttributedText, + level: .warning)) + .createView() + } + } + + @ViewBuilder + private var checkboxRow: some View { + if viewModel.requiresAcknowledgeHighSlippage { + dydxCheckboxView(isChecked: $viewModel.hasAcknowledgedHighSlippage, + text: DataLocalizer.localize(path: "APP.VAULTS.SLIPPAGE_ACK", + params: ["AMOUNT": dydxFormatter.shared.percent(number: viewModel.elevatedSlippageAmount, digits: 2) ?? "--"])) + } + } + + private var submitButton: some View { + let content: Text + let state: PlatformButtonState + switch viewModel.submitState { + case .enabled: + state = .primary + content = Text(viewModel.transferType.confirmTransferText) + .themeColor(foreground: .textPrimary) + .themeFont(fontType: .base, fontSize: .large) + case .disabled: + state = .disabled + content = Text(DataLocalizer.localize(path: "APP.VAULTS.ACKNOWLEDGE_HIGH_SLIPPAGE")) + .themeColor(foreground: .textTertiary) + .themeFont(fontType: .base, fontSize: .large) + } + return PlatformButtonViewModel(content: content.wrappedViewModel, state: state, action: viewModel.submitAction ?? {}) + .createView() + } +} diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/DepositAndWithdrawal/dydxVaultDepositWithdrawViewModel.swift b/dydx/dydxViews/dydxViews/_v4/Vault/DepositAndWithdrawal/dydxVaultDepositWithdrawViewModel.swift index 7df7bae60..94b8e70bc 100644 --- a/dydx/dydxViews/dydxViews/_v4/Vault/DepositAndWithdrawal/dydxVaultDepositWithdrawViewModel.swift +++ b/dydx/dydxViews/dydxViews/_v4/Vault/DepositAndWithdrawal/dydxVaultDepositWithdrawViewModel.swift @@ -21,7 +21,7 @@ public class dydxVaultDepositWithdrawViewModel: PlatformViewModel { @Published public private(set) var numberFormatter = dydxNumberInputFormatter() - @Published fileprivate var selectedTransferType: VaultTransferType + @Published public fileprivate(set) var selectedTransferType: VaultTransferType @Published fileprivate var amount: Double? fileprivate var maxAmount: Double = 0 @@ -43,39 +43,6 @@ public class dydxVaultDepositWithdrawViewModel: PlatformViewModel { } } -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 var enabledText: String { - switch self { - case .deposit: return DataLocalizer.localize(path: "APP.VAULTS.PREVIEW_DEPOSIT") - case .withdraw: return DataLocalizer.localize(path: "APP.VAULTS.PREVIEW_WITHDRAW") - } - } - - fileprivate var disabledText: 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 @@ -142,12 +109,12 @@ fileprivate struct VaultDepositWithdrawView: View { switch viewModel.submitState { case .enabled: state = .primary - content = Text(viewModel.selectedTransferType.enabledText) + content = Text(viewModel.selectedTransferType.previewTransferText) .themeColor(foreground: .textPrimary) .themeFont(fontType: .base, fontSize: .large) case .disabled: state = .disabled - content = Text(viewModel.selectedTransferType.disabledText) + content = Text(viewModel.selectedTransferType.needsAmountText) .themeColor(foreground: .textTertiary) .themeFont(fontType: .base, fontSize: .large) }