Skip to content

Commit

Permalink
MOB-359 Target Leverage Selection Screen
Browse files Browse the repository at this point in the history
  • Loading branch information
ruixhuang committed May 8, 2024
1 parent a90cfa8 commit ef8ef2c
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 5 deletions.
12 changes: 12 additions & 0 deletions dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
0276FA992A0DB8FD000BDF0B /* Model+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0276FA982A0DB8FD000BDF0B /* Model+Ext.swift */; };
0279656A29D795E8004DEB20 /* tabs_v4.json in Resources */ = {isa = PBXBuildFile; fileRef = 0279655B29D795E7004DEB20 /* tabs_v4.json */; };
0279656C29D795E8004DEB20 /* routing_swiftui.json in Resources */ = {isa = PBXBuildFile; fileRef = 0279656929D795E7004DEB20 /* routing_swiftui.json */; };
0279DE482BEBE76900F9ECF8 /* dydxTargetLeverageCtaButtonViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0279DE472BEBE76900F9ECF8 /* dydxTargetLeverageCtaButtonViewPresenter.swift */; };
027CA87229EDFC990069781A /* dydxTransferInputCtaButtonViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027CA87129EDFC990069781A /* dydxTransferInputCtaButtonViewPresenter.swift */; };
027CB28729EEFF910069781A /* dydxTransferStatusViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027CB28629EEFF910069781A /* dydxTransferStatusViewBuilder.swift */; };
027E1EF829CA27CD0098666F /* dydxSettingsLandingViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027E1EF729CA27CD0098666F /* dydxSettingsLandingViewBuilder.swift */; };
Expand Down Expand Up @@ -431,6 +432,7 @@
0276FA982A0DB8FD000BDF0B /* Model+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Model+Ext.swift"; sourceTree = "<group>"; };
0279655B29D795E7004DEB20 /* tabs_v4.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tabs_v4.json; sourceTree = "<group>"; };
0279656929D795E7004DEB20 /* routing_swiftui.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = routing_swiftui.json; sourceTree = "<group>"; };
0279DE472BEBE76900F9ECF8 /* dydxTargetLeverageCtaButtonViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTargetLeverageCtaButtonViewPresenter.swift; sourceTree = "<group>"; };
027CA87129EDFC990069781A /* dydxTransferInputCtaButtonViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTransferInputCtaButtonViewPresenter.swift; sourceTree = "<group>"; };
027CB28629EEFF910069781A /* dydxTransferStatusViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTransferStatusViewBuilder.swift; sourceTree = "<group>"; };
027E1EF729CA27CD0098666F /* dydxSettingsLandingViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSettingsLandingViewBuilder.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -749,6 +751,7 @@
023AB3C22BEAD554005230B2 /* Margin */ = {
isa = PBXGroup;
children = (
0279DE462BEBE75D00F9ECF8 /* Components */,
023AB3C32BEAD56A005230B2 /* dydxMarginModeViewBuilder.swift */,
023AB3C72BEAD5F3005230B2 /* dydxTargetLeverageViewBuilder.swift */,
);
Expand Down Expand Up @@ -1028,6 +1031,14 @@
path = Components;
sourceTree = "<group>";
};
0279DE462BEBE75D00F9ECF8 /* Components */ = {
isa = PBXGroup;
children = (
0279DE472BEBE76900F9ECF8 /* dydxTargetLeverageCtaButtonViewPresenter.swift */,
);
path = Components;
sourceTree = "<group>";
};
027CB26A29EEFF5A0069781A /* TransferStatus */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1898,6 +1909,7 @@
02E90C5A29D62719004E2311 /* dydxFeatureFlagsViewBuilder.swift in Sources */,
6453AB26299D98110041A0C4 /* dydxClosePositionInputEditPresenter.swift in Sources */,
0243A76129BE572C00A083FE /* dydxCancelOrderActionBuilder.swift in Sources */,
0279DE482BEBE76900F9ECF8 /* dydxTargetLeverageCtaButtonViewPresenter.swift in Sources */,
023AB3B22BEACE14005230B2 /* dydxTradeInputMarginViewPresenter.swift in Sources */,
026388D82BB34B7A006DD6E8 /* OnboardingAnalytics.swift in Sources */,
64A4DB9929664818008D8E20 /* dydxTradeReceiptPresenter.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@
},
"/trade/target_leverage":{
"destination":"dydxPresenters.dydxTargetLeverageViewBuilder",
"presentation":"half"
"presentation":"prompt"
},
"/transfer":{
"destination":"dydxPresenters.dydxTransferViewBuilder",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// dydxTargetLeverageCtaButtonViewPresenter.swift
// dydxPresenters
//
// Created by Rui Huang on 08/05/2024.
//

import Utilities
import dydxViews
import PlatformParticles
import RoutingKit
import ParticlesKit
import PlatformUI

protocol dydxTargetLeverageCtaButtonViewPresenterProtocol: HostedViewPresenterProtocol {
var viewModel: dydxTargetLeverageCtaButtonViewModel? { get }
}

class dydxTargetLeverageCtaButtonViewPresenter: HostedViewPresenter<dydxTargetLeverageCtaButtonViewModel>, dydxTargetLeverageCtaButtonViewPresenterProtocol {
override init() {
super.init()

viewModel = dydxTargetLeverageCtaButtonViewModel()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import PlatformParticles
import RoutingKit
import ParticlesKit
import PlatformUI
import Abacus
import dydxStateManager
import dydxFormatter

public class dydxTargetLeverageViewBuilder: NSObject, ObjectBuilderProtocol {
public func build<T>() -> T? {
let presenter = dydxTargetLeverageViewPresenter()
let view = presenter.viewModel?.createView() ?? PlatformViewModel().createView()
return dydxTargetLeverageViewController(presenter: presenter, view: view, configuration: .ignoreSafeArea) as? T
return dydxTargetLeverageViewController(presenter: presenter, view: view, configuration: .default) as? T
}
}

Expand All @@ -34,10 +37,39 @@ private protocol dydxTargetLeverageViewPresenterProtocol: HostedViewPresenterPro
}

private class dydxTargetLeverageViewPresenter: HostedViewPresenter<dydxTargetLeverageViewModel>, dydxTargetLeverageViewPresenterProtocol {
private let ctaButtonPresenter = dydxTargetLeverageCtaButtonViewPresenter()

private lazy var childPresenters: [HostedViewPresenterProtocol] = [
ctaButtonPresenter
]

override init() {
super.init()

viewModel = dydxTargetLeverageViewModel()
viewModel?.description = DataLocalizer.localize(path: "APP.TRADE.ADJUST_TARGET_LEVERAGE_DESCRIPTION")

viewModel?.leverageOptions = [
dydxTargetLeverageViewModel.LeverageTextAndValue(text: "1x", value: 1.0),
dydxTargetLeverageViewModel.LeverageTextAndValue(text: "2x", value: 2.0),
dydxTargetLeverageViewModel.LeverageTextAndValue(text: "5x", value: 5.0),
dydxTargetLeverageViewModel.LeverageTextAndValue(text: "10.0", value: 10.0),
dydxTargetLeverageViewModel.LeverageTextAndValue(text: "Max", value: 20.0)
]

attachChildren(workers: childPresenters)
}

override func start() {
super.start()

// TODO: Fix... tradeInput?.targetLeverage is nil for now

AbacusStateManager.shared.state.tradeInput
.sink { [weak self] tradeInput in
let value = dydxFormatter.shared.localFormatted(number: tradeInput?.targetLeverage ?? 1, digits: 1)
self?.viewModel?.leverageInput?.value = value
}
.store(in: &subscriptions)
}
}
12 changes: 12 additions & 0 deletions dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
0277A21E28BD75E8005A51F8 /* ThemeLight.json in Resources */ = {isa = PBXBuildFile; fileRef = 024B7B5228B7F89500F7C386 /* ThemeLight.json */; };
0277A21F28BD75F9005A51F8 /* dydxStyle.json in Resources */ = {isa = PBXBuildFile; fileRef = 024B7B5328B7F89500F7C386 /* dydxStyle.json */; };
0277A22528BD7B14005A51F8 /* WalletConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0277A22428BD7B14005A51F8 /* WalletConnectionView.swift */; };
0279DE452BEBE75100F9ECF8 /* dydxTargetLeverageCtaButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0279DE442BEBE75100F9ECF8 /* dydxTargetLeverageCtaButtonView.swift */; };
027C379F29AEC11000381B00 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 027C379E29AEC11000381B00 /* Introspect */; };
027CB28529EEFF760069781A /* dydxTransferStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027CB28429EEFF760069781A /* dydxTransferStatusView.swift */; };
027F3EF72AB93ADC00602E5B /* dydxProfileBalancesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027F3EF62AB93ADC00602E5B /* dydxProfileBalancesViewModel.swift */; };
Expand Down Expand Up @@ -460,6 +461,7 @@
0276FA7B2A0DB515000BDF0B /* OrderStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderStatus.swift; sourceTree = "<group>"; };
02770BAB2ABE11D4004BBFE5 /* dydxReceiptRewardsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxReceiptRewardsView.swift; sourceTree = "<group>"; };
0277A22428BD7B14005A51F8 /* WalletConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectionView.swift; sourceTree = "<group>"; };
0279DE442BEBE75100F9ECF8 /* dydxTargetLeverageCtaButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTargetLeverageCtaButtonView.swift; sourceTree = "<group>"; };
027CB28429EEFF760069781A /* dydxTransferStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTransferStatusView.swift; sourceTree = "<group>"; };
027F3EF62AB93ADC00602E5B /* dydxProfileBalancesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxProfileBalancesViewModel.swift; sourceTree = "<group>"; };
0280B39629CB63C70017D64A /* dydxOnboardWelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxOnboardWelcomeView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -778,6 +780,7 @@
023AB3C12BEAD544005230B2 /* Margin */ = {
isa = PBXGroup;
children = (
0279DE412BEBE56C00F9ECF8 /* Components */,
023AB3B32BEAD53D005230B2 /* dydxMarginModeView.swift */,
023AB3C52BEAD5E9005230B2 /* dydxTargetLeverageView.swift */,
);
Expand Down Expand Up @@ -1156,6 +1159,14 @@
path = History;
sourceTree = "<group>";
};
0279DE412BEBE56C00F9ECF8 /* Components */ = {
isa = PBXGroup;
children = (
0279DE442BEBE75100F9ECF8 /* dydxTargetLeverageCtaButtonView.swift */,
);
path = Components;
sourceTree = "<group>";
};
027CB27729EEFF670069781A /* TransferStatus */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1971,6 +1982,7 @@
024FEB642ACB75E10087A55E /* dydxFeesStuctureView.swift in Sources */,
277E918B2B27762F005CCBCB /* dydxRewardsLaunchIncentivesView.swift in Sources */,
0268BBF92A8BE08C00D0C59B /* dydxTransferOutView.swift in Sources */,
0279DE452BEBE75100F9ECF8 /* dydxTargetLeverageCtaButtonView.swift in Sources */,
0208627A28F4D95F00C9D3A0 /* dydxMarketInfoPagingView.swift in Sources */,
027F3EF72AB93ADC00602E5B /* dydxProfileBalancesViewModel.swift in Sources */,
024B44F52983E38D00E35D54 /* dydxTradeStatusLogoView.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// dydxTargetLeverageCtaButtonView.swift
// dydxUI
//
// Created by Rui Huang on 08/05/2024.
// Copyright © 2024 dYdX Trading Inc. All rights reserved.
//

import SwiftUI
import PlatformUI
import Utilities

public class dydxTargetLeverageCtaButtonViewModel: PlatformViewModel {
public enum State {
case enabled(String? = nil)
case disabled(String? = nil)
case thinking
}

@Published public var ctaAction: (() -> Void)?
@Published public var ctaButtonState: State = .disabled()

public init() { }

public static var previewValue: dydxTargetLeverageCtaButtonViewModel {
let vm = dydxTargetLeverageCtaButtonViewModel()
vm.ctaButtonState = .enabled("OK")
return vm
}

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 AnyView(
self.createCtaButton(parentStyle: style)
)
}
}

private func createCtaButton(parentStyle style: ThemeStyle) -> some View {
let buttonTitle: String
let state: PlatformButtonState
switch ctaButtonState {
case .enabled(let title):
buttonTitle = title ?? DataLocalizer.localize(path: "APP.TRADE.CONFIRM_LEVERAGE")
state = .primary
case .disabled(let title):
buttonTitle = title ?? DataLocalizer.localize(path: "APP.TRADE.ADJUST_LEVERAGE")
state = .disabled
case .thinking:
buttonTitle = DataLocalizer.localize(path: "APP.V4.CALCULATING")
state = .disabled
}

let buttonContent =
Text(buttonTitle)
.wrappedViewModel

return PlatformButtonViewModel(content: buttonContent,
state: state) { [weak self] in
PlatformView.hideKeyboard()
self?.ctaAction?()
}
.createView(parentStyle: style)
.animation(.easeInOut(duration: 0.1))
}
}

#if DEBUG
struct dydxTargetLeverageCtaButtonView_Previews_Dark: PreviewProvider {
@StateObject static var themeSettings = ThemeSettings.shared

static var previews: some View {
ThemeSettings.applyDarkTheme()
ThemeSettings.applyStyles()
return dydxTargetLeverageCtaButtonViewModel.previewValue
.createView()
.themeColor(background: .layer0)
.environmentObject(themeSettings)
// .edgesIgnoringSafeArea(.bottom)
.previewLayout(.sizeThatFits)
}
}

struct dydxTargetLeverageCtaButtonView_Previews_Light: PreviewProvider {
@StateObject static var themeSettings = ThemeSettings.shared

static var previews: some View {
ThemeSettings.applyLightTheme()
ThemeSettings.applyStyles()
return dydxTargetLeverageCtaButtonViewModel.previewValue
.createView()
.themeColor(background: .layer0)
.environmentObject(themeSettings)
// .edgesIgnoringSafeArea(.bottom)
.previewLayout(.sizeThatFits)
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,25 @@ import PlatformUI
import Utilities

public class dydxTargetLeverageViewModel: PlatformViewModel {
public struct LeverageTextAndValue {
let text: String
let value: Double

public init(text: String, value: Double) {
self.text = text
self.value = value
}
}

@Published public var description: String?
@Published public var leverageOptions: [LeverageTextAndValue] = []
@Published public var selectedOptionIndex: Int?
@Published public var optionSelectedAction: ((LeverageTextAndValue) -> Void)?
@Published public var leverageInput: PlatformTextInputViewModel? =
PlatformTextInputViewModel(label: DataLocalizer.localize(path: "APP.TRADE.TARGET_LEVERAGE"),
placeHolder: "0.0",
inputType: PlatformTextInputViewModel.InputType.decimalDigits)
@Published public var ctaButton: dydxTargetLeverageCtaButtonViewModel? = dydxTargetLeverageCtaButtonViewModel()

public init() { }

Expand All @@ -22,7 +40,7 @@ public class dydxTargetLeverageViewModel: PlatformViewModel {
}

public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView {
PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] _ in
PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in
guard let self = self else { return AnyView(PlatformView.nilView) }

let view = VStack(alignment: .leading, spacing: 20) {
Expand All @@ -37,16 +55,57 @@ public class dydxTargetLeverageViewModel: PlatformViewModel {
.leftAligned()
.themeFont(fontSize: .medium)

self.leverageInput?
.createView(parentStyle: style)
.makeInput()

self.createOptionsGroup(parentStyle: style)

Spacer()

self.ctaButton?.createView(parentStyle: style)
}
.padding(.horizontal)
.themeColor(background: .layer3)
.makeSheet(sheetStyle: .fitSize)

// make it visible under the tabbar
return AnyView(view.ignoresSafeArea(edges: [.bottom]))
return AnyView(view)
}
}

private func createOptionsGroup(parentStyle: ThemeStyle) -> some View {
let items = self.leverageOptions.compactMap {
Text($0.text)
.themeFont(fontType: .plus, fontSize: .small)
.themeColor(foreground: .textTertiary)
.padding(8)
.frame(minWidth: 60)
.themeColor(background: .layer5)
.border(borderWidth: 1, cornerRadius: 8, borderColor: ThemeColor.SemanticColor.layer5.color)
.wrappedViewModel
}
let selectedItems = self.leverageOptions.compactMap {
Text($0.text)
.themeFont(fontType: .plus, fontSize: .small)
.themeColor(foreground: .textPrimary)
.padding(8)
.frame(minWidth: 60)
.themeColor(background: .layer1)
.border(borderWidth: 1, cornerRadius: 8, borderColor: ThemeColor.SemanticColor.layer5.color)
.wrappedViewModel
}

return
ScrollView(.horizontal, showsIndicators: false) {
TabGroupModel(items: items,
selectedItems: selectedItems,
currentSelection: self.selectedOptionIndex,
onSelectionChanged: { index in
self.optionSelectedAction?(self.leverageOptions[index])
})
.createView(parentStyle: parentStyle)
}
}
}

#if DEBUG
Expand Down

0 comments on commit ef8ef2c

Please sign in to comment.