diff --git a/dydx/dydxFormatter/dydxFormatter/dydxFormatter.swift b/dydx/dydxFormatter/dydxFormatter/dydxFormatter.swift index 06f7dd94..30173c04 100644 --- a/dydx/dydxFormatter/dydxFormatter/dydxFormatter.swift +++ b/dydx/dydxFormatter/dydxFormatter/dydxFormatter.swift @@ -367,12 +367,12 @@ public final class dydxFormatter: NSObject, SingletonProtocol { } /* - xxxxxx,yyyyy or xxxxx.yyyyy - will take the number and round it to the closest step size - e.g. if number is 1021 and step size is "100" then output is "1000" + converts number in multiplier, e.g. + 1.20 -> 1.2x + removes trailing 0s as well */ - public func multiplier(number: Double?) -> String? { - if let formattedText = dydxFormatter.shared.raw(number: number, digits: 2) { + public func multiplier(number: Double?, maxPrecision: Int = 2) -> String? { + if let formattedText = dydxFormatter.shared.raw(number: number, minDigits: 0, maxDigits: maxPrecision) { return "\(formattedText)×" } else { return nil @@ -421,19 +421,26 @@ public final class dydxFormatter: NSObject, SingletonProtocol { public func raw(number: Double?, digits: Int, locale: Locale = Locale.current) -> String? { guard let number = number else { return nil } - return raw(number: NSNumber(value: number), digits: digits, locale: locale) + return raw(number: NSNumber(value: number), minDigits: digits, maxDigits: digits, locale: locale) + } + + public func raw(number: Double?, minDigits: Int, maxDigits: Int, locale: Locale = Locale.current) -> String? { + guard let number = number else { return nil } + return raw(number: NSNumber(value: number), minDigits: minDigits, maxDigits: maxDigits, locale: locale) } - /* - xxxxxx,yyyyy or xxxxx.yyyyy - */ public func raw(number: NSNumber?, digits: Int, locale: Locale = Locale.current) -> String? { + guard let number = number else { return nil } + return raw(number: number, minDigits: digits, maxDigits: digits, locale: locale) + } + + public func raw(number: NSNumber?, minDigits: Int, maxDigits: Int, locale: Locale = Locale.current) -> String? { if let value = number?.doubleValue { if value.isFinite { if let number = number { rawFormatter.locale = locale - rawFormatter.minimumFractionDigits = max(digits, 0) - rawFormatter.maximumFractionDigits = max(digits, 0) + rawFormatter.minimumFractionDigits = max(minDigits, 0) + rawFormatter.maximumFractionDigits = max(maxDigits, 0) rawFormatter.roundingMode = .halfUp let formatted = rawFormatter.string(from: number) diff --git a/dydx/dydxFormatter/dydxFormatterTests/dydxFormatterTests.swift b/dydx/dydxFormatter/dydxFormatterTests/dydxFormatterTests.swift index f0445cd7..60cc81f8 100644 --- a/dydx/dydxFormatter/dydxFormatterTests/dydxFormatterTests.swift +++ b/dydx/dydxFormatter/dydxFormatterTests/dydxFormatterTests.swift @@ -145,6 +145,32 @@ final class dydxFormatterTests: XCTestCase { } } + func testMultiplierFormatting() throws { + struct TestCase { + let number: Double + let maxPrecision: Int + let expected: String + } + + let testCases: [TestCase] = [ + .init(number: 0.01, maxPrecision: 2, expected: "0.01×"), + .init(number: -0.01, maxPrecision: 2, expected: "-0.01×"), + .init(number: 0.01, maxPrecision: 3, expected: "0.01×"), + .init(number: -0.01, maxPrecision: 3, expected: "-0.01×"), + .init(number: 0.01, maxPrecision: 1, expected: "0×"), + .init(number: -0.01, maxPrecision: 1, expected: "0×"), + .init(number: 10, maxPrecision: 3, expected: "10×"), + .init(number: -10, maxPrecision: 3, expected: "-10×") + ] + + for testCase in testCases { + let formatted = dydxFormatter.shared.multiplier(number: testCase.number, maxPrecision: testCase.maxPrecision) + print(testCase) + XCTAssertEqual(formatted, testCase.expected) + print() + } + } + func testPerformanceExample() throws { // This is an example of a performance test case. self.measure { diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Receipt/dydxTradeReceiptPresenter.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Receipt/dydxTradeReceiptPresenter.swift index c23f4401..a022e574 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Receipt/dydxTradeReceiptPresenter.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Receipt/dydxTradeReceiptPresenter.swift @@ -110,15 +110,16 @@ final class dydxTradeReceiptPresenter: dydxReceiptPresenter { let title = DataLocalizer.localize(path: "APP.TRADE.POSITION_LEVERAGE") let unit = AmountTextModel.Unit.multiplier positionLeverageViewModel.title = title - positionLeverageViewModel.value = createAmountChangeViewModel(title: title, tradeState: position?.leverage, tickSize: 2, unit: unit) + positionLeverageViewModel.value = createAmountChangeViewModel(title: title, tradeState: position?.leverage, tickSize: 2, unit: unit, shouldUseAbsoluteValues: true) } private func createAmountChangeViewModel(title: String, - tradeState: TradeStatesWithDoubleValues?, - tickSize: NSNumber?, - unit: AmountTextModel.Unit) -> AmountChangeModel { - let currentValue = tradeState?.current?.doubleValue.asNsNumber - let postValue = tradeState?.postOrder?.doubleValue.asNsNumber + tradeState: TradeStatesWithDoubleValues?, + tickSize: NSNumber?, + unit: AmountTextModel.Unit, + shouldUseAbsoluteValues: Bool = false) -> AmountChangeModel { + let currentValue = shouldUseAbsoluteValues ? tradeState?.current?.doubleValue.asNsNumber.abs() : tradeState?.current?.doubleValue.asNsNumber + let postValue = shouldUseAbsoluteValues ? tradeState?.postOrder?.doubleValue.asNsNumber.abs() : tradeState?.postOrder?.doubleValue.asNsNumber let currentViewModel = currentValue == nil ? nil : AmountTextModel(amount: currentValue, unit: unit) let postViewModel = postValue == nil ? nil : AmountTextModel(amount: postValue, unit: unit) return AmountChangeModel(before: currentViewModel, after: postViewModel) diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Trade/Margin/dydxAdjustMarginInputViewBuilder.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Trade/Margin/dydxAdjustMarginInputViewBuilder.swift index 84c9632d..0b9d62f1 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Trade/Margin/dydxAdjustMarginInputViewBuilder.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Trade/Margin/dydxAdjustMarginInputViewBuilder.swift @@ -154,18 +154,18 @@ private class dydxAdjustMarginInputViewPresenter: HostedViewPresenter= freeCollateral { - return "ERRORS.TRANSFER_MODAL.TRANSFER_MORE_THAN_FREE" - } - if let positionMarginUpdated = input.summary?.positionMarginUpdated?.doubleValue, positionMarginUpdated < 0 { - return "ERRORS.TRADE_BOX.INVALID_NEW_ACCOUNT_MARGIN_USAGE" - } if let effectiveInitialMarginFraction = market.configs?.effectiveInitialMarginFraction?.doubleValue, effectiveInitialMarginFraction > 0 { let marketMaxLeverage = 1 / effectiveInitialMarginFraction if let positionLeverageUpdated = input.summary?.positionLeverageUpdated?.doubleValue, positionLeverageUpdated > marketMaxLeverage { return "ERRORS.TRADE_BOX_TITLE.INVALID_NEW_POSITION_LEVERAGE" } } + if let positionMarginUpdated = input.summary?.positionMarginUpdated?.doubleValue, positionMarginUpdated < 0 { + return "ERRORS.TRADE_BOX.INVALID_NEW_ACCOUNT_MARGIN_USAGE" + } + if let freeCollateral = input.summary?.crossFreeCollateral?.doubleValue, amount >= freeCollateral { + return "ERRORS.TRANSFER_MODAL.TRANSFER_MORE_THAN_FREE" + } default: break } @@ -208,11 +208,12 @@ private class dydxAdjustMarginInputViewPresenter: HostedViewPresenter 0 { + if let imf = market.configs?.effectiveInitialMarginFraction?.doubleValue, imf > 0 { let maxLeverage = 1.0 / imf - viewModel.leverageOptions = [1, 2, 5, 10] + viewModel.leverageOptions = [1, 2, 3, 5, 10] .filter { $0 < maxLeverage } .map { dydxTargetLeverageViewModel.LeverageTextAndValue(text: dydxFormatter.shared.multiplier(number: Double($0)) ?? "", value: $0) } viewModel.leverageOptions.append(dydxTargetLeverageViewModel.LeverageTextAndValue(text: DataLocalizer.localize(path: "APP.GENERAL.MAX"), value: maxLeverage)) diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/Trade/TradeInput/Components/TradeInputFields/dydxTradeInputMarginViewPresenter.swift b/dydx/dydxPresenters/dydxPresenters/_v4/Trade/TradeInput/Components/TradeInputFields/dydxTradeInputMarginViewPresenter.swift index f828e79d..ab10ce2a 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/Trade/TradeInput/Components/TradeInputFields/dydxTradeInputMarginViewPresenter.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/Trade/TradeInput/Components/TradeInputFields/dydxTradeInputMarginViewPresenter.swift @@ -47,6 +47,6 @@ class dydxTradeInputMarginViewPresenter: HostedViewPresenter { - public convenience init(before: AmountTextModel?, after: AmountTextModel?) { + public convenience init(before: AmountTextModel?, after: AmountTextModel?, increasingIsPositiveDirection: Bool = true) { self.init() self.before = before self.after = after + + changeDirection = { [weak self] in + guard let beforeAmount = self?.before?.amount, let afterAmount = self?.after?.amount else { + return .orderedSame + } + if increasingIsPositiveDirection { + return beforeAmount.compare(afterAmount) + } else { + return afterAmount.compare(beforeAmount) + } + } } public required init(unit: AmountTextModel.Unit = .dollar) { diff --git a/dydx/dydxViews/dydxViews/_v4/Trade/Margin/dydxMarginModeView.swift b/dydx/dydxViews/dydxViews/_v4/Trade/Margin/dydxMarginModeView.swift index 72b01a7b..dc99b2c5 100644 --- a/dydx/dydxViews/dydxViews/_v4/Trade/Margin/dydxMarginModeView.swift +++ b/dydx/dydxViews/dydxViews/_v4/Trade/Margin/dydxMarginModeView.swift @@ -93,6 +93,7 @@ public class dydxMarginModeItemViewModel: PlatformViewModel { public class dydxMarginModeViewModel: PlatformViewModel { @Published public var market: String? @Published public var items: [dydxMarginModeItemViewModel] = [] + @Published public var isDisabled: Bool = false public init() { } @@ -125,9 +126,14 @@ public class dydxMarginModeViewModel: PlatformViewModel { } } + if self.isDisabled { + InlineAlertViewModel(.init(title: nil, body: DataLocalizer.localize(path: "WARNINGS.TRADE_BOX.UNABLE_TO_CHANGE_MARGIN_MODE", params: ["MARKET": self.market ?? "--"]), level: .warning)) + .createView() + } Spacer() } .padding(.horizontal) + .padding(.bottom, max((self.safeAreaInsets?.bottom ?? 0), 16)) .themeColor(background: .layer3) .makeSheet(sheetStyle: .fitSize) diff --git a/dydx/dydxViews/dydxViews/_v4/Trade/Margin/dydxTargetLeverageView.swift b/dydx/dydxViews/dydxViews/_v4/Trade/Margin/dydxTargetLeverageView.swift index cbb89584..d727ff2c 100644 --- a/dydx/dydxViews/dydxViews/_v4/Trade/Margin/dydxTargetLeverageView.swift +++ b/dydx/dydxViews/dydxViews/_v4/Trade/Margin/dydxTargetLeverageView.swift @@ -79,7 +79,7 @@ public class dydxTargetLeverageViewModel: PlatformViewModel { private func createOptionsGroup(parentStyle: ThemeStyle) -> some View { let spacing: CGFloat = 8 - let maxItemsToDisplay = CGFloat(min(5, leverageOptions.count)) + let maxItemsToDisplay = CGFloat(leverageOptions.count) return SingleAxisGeometryReader { width in let width = (width + spacing) / maxItemsToDisplay - spacing diff --git a/dydxV4/dydxV4/_Tracking/TrackingViewController+Ext.swift b/dydxV4/dydxV4/_Tracking/TrackingViewController+Ext.swift index 2da465db..c760c972 100644 --- a/dydxV4/dydxV4/_Tracking/TrackingViewController+Ext.swift +++ b/dydxV4/dydxV4/_Tracking/TrackingViewController+Ext.swift @@ -48,7 +48,7 @@ extension TrackingViewController: ScreenIdentifiable, TrackingViewProtocol { private extension TrackingViewController { private var path: String { guard let path = history?.path else { - assertionFailure("no path for \(screenClass)") +// assertionFailure("no path for \(screenClass)") return "" } return path