Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MarketInfo content update for Simple UI #337

Merged
merged 6 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
02048B122D35E3DF00394CBE /* dydxSimpleUIPositionListViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02048B112D35E3DE00394CBE /* dydxSimpleUIPositionListViewPresenter.swift */; };
02048BBD2D372AAE00394CBE /* dydxProfileTopButtonsViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02048BBC2D372AAD00394CBE /* dydxProfileTopButtonsViewPresenter.swift */; };
02048BC32D382B9500394CBE /* dydxSimpleUIMarketSearchViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02048BC22D382B9300394CBE /* dydxSimpleUIMarketSearchViewBuilder.swift */; };
02048E212D38A11000394CBE /* dydxSimpleUIMarketDetailsViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02048E132D38A10E00394CBE /* dydxSimpleUIMarketDetailsViewPresenter.swift */; };
02048E272D398D7E00394CBE /* dydxMarketTpSlGroupViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02048E262D398D7C00394CBE /* dydxMarketTpSlGroupViewPresenter.swift */; };
02048E5C2D39BCF700394CBE /* dydxSimpleUIMarketBuySellViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02048E4E2D39BCF600394CBE /* dydxSimpleUIMarketBuySellViewPresenter.swift */; };
0207FC9D2D269C00004C2C9F /* dydxSimpleUITradeInputValidationViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0207FC8F2D269BFF004C2C9F /* dydxSimpleUITradeInputValidationViewPresenter.swift */; };
0207FC9F2D27BA6B004C2C9F /* dydxSimpleUITradeInputCtaButtonViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0207FC9E2D27BA62004C2C9F /* dydxSimpleUITradeInputCtaButtonViewPresenter.swift */; };
0207FCA32D2A181D004C2C9F /* dydxSimpleUIMarketsHeaderViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0207FCA22D2A181C004C2C9F /* dydxSimpleUIMarketsHeaderViewPresenter.swift */; };
Expand Down Expand Up @@ -428,6 +431,9 @@
02048B112D35E3DE00394CBE /* dydxSimpleUIPositionListViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSimpleUIPositionListViewPresenter.swift; sourceTree = "<group>"; };
02048BBC2D372AAD00394CBE /* dydxProfileTopButtonsViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxProfileTopButtonsViewPresenter.swift; sourceTree = "<group>"; };
02048BC22D382B9300394CBE /* dydxSimpleUIMarketSearchViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSimpleUIMarketSearchViewBuilder.swift; sourceTree = "<group>"; };
02048E132D38A10E00394CBE /* dydxSimpleUIMarketDetailsViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSimpleUIMarketDetailsViewPresenter.swift; sourceTree = "<group>"; };
02048E262D398D7C00394CBE /* dydxMarketTpSlGroupViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxMarketTpSlGroupViewPresenter.swift; sourceTree = "<group>"; };
02048E4E2D39BCF600394CBE /* dydxSimpleUIMarketBuySellViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSimpleUIMarketBuySellViewPresenter.swift; sourceTree = "<group>"; };
0207FC8F2D269BFF004C2C9F /* dydxSimpleUITradeInputValidationViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSimpleUITradeInputValidationViewPresenter.swift; sourceTree = "<group>"; };
0207FC9E2D27BA62004C2C9F /* dydxSimpleUITradeInputCtaButtonViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSimpleUITradeInputCtaButtonViewPresenter.swift; sourceTree = "<group>"; };
0207FCA22D2A181C004C2C9F /* dydxSimpleUIMarketsHeaderViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSimpleUIMarketsHeaderViewPresenter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1131,6 +1137,7 @@
0208627B28F4DAC000C9D3A0 /* dydxMarketInfoPagingViewPresenter.swift */,
025D22D528F65E1B00C4ADAE /* dydxMarketStatsViewPresenter.swift */,
024F47912964A83700E40247 /* dydxMarketConfigsViewPresenter.swift */,
02048E262D398D7C00394CBE /* dydxMarketTpSlGroupViewPresenter.swift */,
0238FE09296EF91D002E1C1A /* dydxMarketPositionViewPresenter.swift */,
);
path = Components;
Expand All @@ -1148,6 +1155,8 @@
027885EE2D1DE13C00366321 /* components */ = {
isa = PBXGroup;
children = (
02048E4E2D39BCF600394CBE /* dydxSimpleUIMarketBuySellViewPresenter.swift */,
02048E132D38A10E00394CBE /* dydxSimpleUIMarketDetailsViewPresenter.swift */,
027885EF2D1DE14500366321 /* dydxSimpleUIMarketInfoHeaderViewPresenter.swift */,
027885F32D1DEBF600366321 /* dydxSimpleUIMarketCandlesViewPresenter.swift */,
027885FB2D1E171B00366321 /* dydxSimpleUIMarketPositionViewPresenter.swift */,
Expand Down Expand Up @@ -2157,6 +2166,7 @@
27351D452AC4A67900E4A563 /* dydxRestrictionsWorker.swift in Sources */,
6496DC39295E081300174CE7 /* dydxPortfolioViewBuilder.swift in Sources */,
0207FC9D2D269C00004C2C9F /* dydxSimpleUITradeInputValidationViewPresenter.swift in Sources */,
02048E212D38A11000394CBE /* dydxSimpleUIMarketDetailsViewPresenter.swift in Sources */,
02465B96297F6E2E00A4CA55 /* dydxPortfolioHeaderPresenter.swift in Sources */,
0238FE0A296EF91D002E1C1A /* dydxMarketPositionViewPresenter.swift in Sources */,
27592E052C9A57D4002FBD4B /* dydxAlertsViewBuilder.swift in Sources */,
Expand Down Expand Up @@ -2205,6 +2215,7 @@
0280B3A629CB63E10017D64A /* dydxOnboardWelcomeViewBuilder.swift in Sources */,
023848D72A9E785900B1A673 /* dydxTosViewBuilder.swift in Sources */,
0238FC4C296DA55A002E1C1A /* dydxOrderDetailsViewBuilder.swift in Sources */,
02048E272D398D7E00394CBE /* dydxMarketTpSlGroupViewPresenter.swift in Sources */,
02F95A7E2A1D314400828F9A /* dydxPortfolioSelectorViewPresenter.swift in Sources */,
020EB697299D36AD00E8026B /* dydxApiStatusWorker.swift in Sources */,
02A5C85E297FBCD700FFE1F9 /* dydxTransferViewBuilder.swift in Sources */,
Expand Down Expand Up @@ -2267,6 +2278,7 @@
024F488729657FE600E40247 /* dydxUserFavoriteViewPresenter.swift in Sources */,
029CBE7428F6032C00259C1D /* HistoricalFundingDataPoint.swift in Sources */,
0274B34828F1140D005AF69E /* dydxMarketPriceCandlesViewPresenter.swift in Sources */,
02048E5C2D39BCF700394CBE /* dydxSimpleUIMarketBuySellViewPresenter.swift in Sources */,
02404FAA2CD42E8C001B571D /* dydxVaultTosViewBuilder.swift in Sources */,
0253179729C1270700D6CC9B /* dydxTradingNetworkViewBuilder.swift in Sources */,
027885E82D1DD67200366321 /* dydxSimpleUIMarketInfoViewBuilder.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,19 @@ protocol dydxMarketPositionViewPresenterProtocol: HostedViewPresenterProtocol {
}

class dydxMarketPositionViewPresenter: HostedViewPresenter<dydxMarketPositionViewModel>, dydxMarketPositionViewPresenterProtocol {
@Published var position: SubaccountPosition?
@Published var position: SubaccountPosition? {
didSet {
tpSlPresenter.position = position
}
}
@Published var pendingPosition: SubaccountPendingPosition?

private let tpSlPresenter = dydxMarketTpSlGroupViewPresenter()

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

init(viewModel: dydxMarketPositionViewModel?) {
super.init()

Expand All @@ -34,11 +44,19 @@ class dydxMarketPositionViewPresenter: HostedViewPresenter<dydxMarketPositionVie
Router.shared?.navigate(to: RoutingRequest(path: "/trade/close", params: ["marketId": "\(marketId)"]), animated: true, completion: nil)
}
}

attachChildren(workers: childPresenters)
}

override func start() {
super.start()

tpSlPresenter.$viewModel
.sink { [weak self] tpSlGroupViewModel in
self?.viewModel?.tpSlGroupViewModel = tpSlGroupViewModel
}
.store(in: &subscriptions)

Publishers
.CombineLatest3($pendingPosition,
AbacusStateManager.shared.state.marketMap,
Expand Down Expand Up @@ -131,72 +149,5 @@ class dydxMarketPositionViewPresenter: HostedViewPresenter<dydxMarketPositionVie
viewModel?.closePrice = dydxFormatter.shared.dollar(number: position.exitPrice?.doubleValue, digits: configs.displayTickSizeDecimals?.intValue ?? 0)

viewModel?.funding = SignedAmountViewModel(amount: position.netFunding?.doubleValue, displayType: .dollar, coloringOption: .allText)

let routeToTakeProfitStopLossAction = {[weak self] in
if let marketId = self?.position?.id {
Router.shared?.navigate(to: RoutingRequest(path: "/trade/take_profit_stop_loss", params: ["marketId": "\(marketId)"]), animated: true, completion: nil)
}
}
let routeToOrdersAction = {
Router.shared?.navigate(to: RoutingRequest(path: "/market", params: ["currentSection": "orders"]), animated: true, completion: nil)
return
}

let takeProfitOrders = triggerOrders.filter { (order: SubaccountOrder) in
order.marketId == position.id
&& (order.type == .takeprofitmarket || (order.type == .takeprofitlimit && AbacusStateManager.shared.environment?.featureFlags.isSlTpLimitOrdersEnabled == true))
&& order.side.opposite == position.side.current
}
let stopLossOrders = triggerOrders.filter { (order: SubaccountOrder) in
order.marketId == position.id
&& (order.type == .stopmarket || (order.type == .stoplimit && AbacusStateManager.shared.environment?.featureFlags.isSlTpLimitOrdersEnabled == true))
&& order.side.opposite == position.side.current
}
if takeProfitOrders.isEmpty && stopLossOrders.isEmpty {
viewModel?.takeProfitStatusViewModel = nil
viewModel?.stopLossStatusViewModel = nil
} else {
let decimalDigits = market.configs?.tickSizeDecimals?.intValue ?? 0
if takeProfitOrders.count > 1 {
viewModel?.takeProfitStatusViewModel = .init(
triggerSide: .takeProfit,
triggerPriceText: DataLocalizer.shared?.localize(path: "APP.TRADE.MULTIPLE_ARROW", params: nil),
action: routeToOrdersAction)
} else if let takeProfitOrder = takeProfitOrders.first, let positionSize = position.size.current?.doubleValue.magnitude {
let orderSize = takeProfitOrder.size.magnitude
viewModel?.takeProfitStatusViewModel = .init(
triggerSide: .takeProfit,
triggerPriceText: dydxFormatter.shared.dollar(number: takeProfitOrder.triggerPrice?.doubleValue, digits: decimalDigits),
limitPrice: takeProfitOrder.type == .takeprofitlimit ? dydxFormatter.shared.dollar(number: takeProfitOrder.price, digits: decimalDigits) : nil,
amount: positionSize == orderSize && positionSize > 0 ? nil : dydxFormatter.shared.percent(number: orderSize / positionSize, digits: 2),
action: routeToTakeProfitStopLossAction)
} else {
viewModel?.takeProfitStatusViewModel = .init(
triggerSide: .takeProfit,
action: routeToTakeProfitStopLossAction)
}

if stopLossOrders.count > 1 {
viewModel?.stopLossStatusViewModel = .init(
triggerSide: .stopLoss,
triggerPriceText: DataLocalizer.shared?.localize(path: "APP.TRADE.MULTIPLE_ARROW", params: nil),
action: routeToOrdersAction)
} else if let stopLossOrder = stopLossOrders.first, let positionSize = position.size.current?.doubleValue.magnitude {
let orderSize = stopLossOrder.size.magnitude
viewModel?.stopLossStatusViewModel = .init(
triggerSide: .stopLoss,
triggerPriceText: dydxFormatter.shared.dollar(number: stopLossOrder.triggerPrice?.doubleValue, digits: decimalDigits),
limitPrice: stopLossOrder.type == .stoplimit ? dydxFormatter.shared.dollar(number: stopLossOrder.price, digits: decimalDigits) : nil,
// don't show amount unless order size is custom
amount: positionSize == orderSize && positionSize > 0 ? nil : dydxFormatter.shared.percent(number: orderSize / positionSize, digits: 2),
action: routeToTakeProfitStopLossAction)
} else {
viewModel?.stopLossStatusViewModel = .init(
triggerSide: .stopLoss,
action: routeToTakeProfitStopLossAction)
}
}

viewModel?.takeProfitStopLossAction = routeToTakeProfitStopLossAction
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//
// dydxMarketTpSlGroupViewPresenter.swift
// dydxPresenters
//
// Created by Rui Huang on 16/01/2025.
//

import Utilities
import dydxViews
import PlatformParticles
import RoutingKit
import ParticlesKit
import PlatformUI
import Abacus
import Combine
import dydxStateManager
import dydxFormatter

protocol dydxMarketTpSlGroupViewPresenterProtocol: HostedViewPresenterProtocol {
var viewModel: dydxMarketTpSlGroupViewModel? { get }
}

class dydxMarketTpSlGroupViewPresenter: HostedViewPresenter<dydxMarketTpSlGroupViewModel>, dydxMarketTpSlGroupViewPresenterProtocol {
@Published var position: SubaccountPosition?

override init() {
super.init()

viewModel = dydxMarketTpSlGroupViewModel()
}

override func start() {
super.start()

Publishers
.CombineLatest4($position.compactMap { $0 }.removeDuplicates(),
AbacusStateManager.shared.state.selectedSubaccountTriggerOrders,
AbacusStateManager.shared.state.marketMap,
AbacusStateManager.shared.state.assetMap)
.sink { [weak self] position, triggerOrders, marketMap, assetMap in
self?.updatePosition(position: position, triggerOrders: triggerOrders, marketMap: marketMap, assetMap: assetMap)
}
.store(in: &subscriptions)
}

private func updatePosition(position: SubaccountPosition, triggerOrders: [SubaccountOrder], marketMap: [String: PerpetualMarket], assetMap: [String: Asset]) {
guard let market = marketMap[position.id] else {
return
}

let routeToTakeProfitStopLossAction = {[weak self] in
if let marketId = self?.position?.id {
Router.shared?.navigate(to: RoutingRequest(path: "/trade/take_profit_stop_loss", params: ["marketId": marketId]), animated: true, completion: nil)
}
}
let routeToOrdersAction = {
Router.shared?.navigate(to: RoutingRequest(path: "/market", params: ["currentSection": "orders"]), animated: true, completion: nil)
return
}

let takeProfitOrders = triggerOrders.filter { (order: SubaccountOrder) in
order.marketId == position.id
&& (order.type == .takeprofitmarket || (order.type == .takeprofitlimit && AbacusStateManager.shared.environment?.featureFlags.isSlTpLimitOrdersEnabled == true))
&& order.side.opposite == position.side.current
}
let stopLossOrders = triggerOrders.filter { (order: SubaccountOrder) in
order.marketId == position.id
&& (order.type == .stopmarket || (order.type == .stoplimit && AbacusStateManager.shared.environment?.featureFlags.isSlTpLimitOrdersEnabled == true))
&& order.side.opposite == position.side.current
}
if takeProfitOrders.isEmpty && stopLossOrders.isEmpty {
viewModel?.takeProfitStatusViewModel = nil
viewModel?.stopLossStatusViewModel = nil
} else {
let decimalDigits = market.configs?.tickSizeDecimals?.intValue ?? 0
if takeProfitOrders.count > 1 {
viewModel?.takeProfitStatusViewModel = .init(
triggerSide: .takeProfit,
triggerPriceText: DataLocalizer.shared?.localize(path: "APP.TRADE.MULTIPLE_ARROW", params: nil),
action: routeToOrdersAction)
} else if let takeProfitOrder = takeProfitOrders.first, let positionSize = position.size.current?.doubleValue.magnitude {
let orderSize = takeProfitOrder.size.magnitude
viewModel?.takeProfitStatusViewModel = .init(
triggerSide: .takeProfit,
triggerPriceText: dydxFormatter.shared.dollar(number: takeProfitOrder.triggerPrice?.doubleValue, digits: decimalDigits),
limitPrice: takeProfitOrder.type == .takeprofitlimit ? dydxFormatter.shared.dollar(number: takeProfitOrder.price, digits: decimalDigits) : nil,
amount: positionSize == orderSize && positionSize > 0 ? nil : dydxFormatter.shared.percent(number: orderSize / positionSize, digits: 2),
action: routeToTakeProfitStopLossAction)
} else {
viewModel?.takeProfitStatusViewModel = .init(
triggerSide: .takeProfit,
action: routeToTakeProfitStopLossAction)
}

if stopLossOrders.count > 1 {
viewModel?.stopLossStatusViewModel = .init(
triggerSide: .stopLoss,
triggerPriceText: DataLocalizer.shared?.localize(path: "APP.TRADE.MULTIPLE_ARROW", params: nil),
action: routeToOrdersAction)
} else if let stopLossOrder = stopLossOrders.first, let positionSize = position.size.current?.doubleValue.magnitude {
let orderSize = stopLossOrder.size.magnitude
viewModel?.stopLossStatusViewModel = .init(
triggerSide: .stopLoss,
triggerPriceText: dydxFormatter.shared.dollar(number: stopLossOrder.triggerPrice?.doubleValue, digits: decimalDigits),
limitPrice: stopLossOrder.type == .stoplimit ? dydxFormatter.shared.dollar(number: stopLossOrder.price, digits: decimalDigits) : nil,
// don't show amount unless order size is custom
amount: positionSize == orderSize && positionSize > 0 ? nil : dydxFormatter.shared.percent(number: orderSize / positionSize, digits: 2),
action: routeToTakeProfitStopLossAction)
} else {
viewModel?.stopLossStatusViewModel = .init(
triggerSide: .stopLoss,
action: routeToTakeProfitStopLossAction)
}
}

viewModel?.takeProfitStopLossAction = routeToTakeProfitStopLossAction
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ class SharedMarketPresenter: HostedViewPresenter<SharedMarketViewModel>, SharedM
if let coinMarketCapsLink = asset?.resources?.coinMarketCapsLink {
viewModel.coinMarketPlaceUrl = URL(string: coinMarketCapsLink)
}
viewModel.openInterest = dydxFormatter.shared.dollarVolume(number: market.perpetual?.openInterest)
if let nextFundingAtMilliseconds = market.perpetual?.nextFundingAtMilliseconds {
let nextFundingAt = Date(milliseconds: nextFundingAtMilliseconds.doubleValue)
viewModel.nextFunding = IntervalTextModel(date: nextFundingAt, direction: .countDown, format: .full)
} else {
// With no nextFundingAt, we will just count down to the next hour mark
viewModel.nextFunding = IntervalTextModel(date: nil, direction: .countDownToHour, format: .full)
}

return viewModel
}
}
Loading
Loading