From 523354c0a969fc8e6602faa828bf98d066fa4234 Mon Sep 17 00:00:00 2001 From: mike-dydx Date: Thu, 13 Jun 2024 14:59:12 -0500 Subject: [PATCH] wire up abacus data for unopened positions --- .../dydxMarketPositionViewPresenter.swift | 2 +- .../dydxPortfolioPositionsViewPresenter.swift | 73 +++++++++++++++++-- .../AbacusState+Combine.swift | 11 +++ .../dydxViews.xcodeproj/project.pbxproj | 8 +- ...tfolioPendingPositionsItemViewModel.swift} | 31 ++++++-- .../Sections/dydxPortfolioPositionsView.swift | 43 ++++++----- 6 files changed, 129 insertions(+), 39 deletions(-) rename dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/{dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift => dydxPortfolioPendingPositionsItemViewModel.swift} (77%) diff --git a/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/Components/dydxMarketPositionViewPresenter.swift b/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/Components/dydxMarketPositionViewPresenter.swift index 4644bf1c2..a18781b1f 100644 --- a/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/Components/dydxMarketPositionViewPresenter.swift +++ b/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/Components/dydxMarketPositionViewPresenter.swift @@ -50,7 +50,7 @@ class dydxMarketPositionViewPresenter: HostedViewPresenter, dydxPortfolioPositionsViewPresenterProtocol { - private var cache = [String: dydxPortfolioPositionItemViewModel]() + private var positionsCache = [String: dydxPortfolioPositionItemViewModel]() + private var pendingPositionsCache = [String: dydxPortfolioPendingPositionsItemViewModel]() init(viewModel: dydxPortfolioPositionsViewModel?) { super.init() @@ -45,33 +46,89 @@ class dydxPortfolioPositionsViewPresenter: HostedViewPresenter dydxPortfolioPositionItemViewModel? in - let item = Self.createViewModelItem(position: position, marketMap: marketMap, assetMap: assetMap, cache: cache) - cache[position.assetId] = item + let item = Self.createPositionViewModelItem(position: position, + marketMap: marketMap, + assetMap: assetMap, + positionsCache: positionsCache) + positionsCache[position.assetId] = item return item } self.viewModel?.positionItems = items - self.viewModel?.unopenedIsolatedPositionItems = [.previewValue, .previewValue, .previewValue] } - static func createViewModelItem(position: SubaccountPosition, marketMap: [String: PerpetualMarket], assetMap: [String: Asset], cache: [String: dydxPortfolioPositionItemViewModel]? = nil) -> dydxPortfolioPositionItemViewModel? { + private func updatePendingPositions(pendingPositions: [SubaccountPendingPosition], marketMap: [String: PerpetualMarket], assetMap: [String: Asset]) { + let items: [dydxPortfolioPendingPositionsItemViewModel] = pendingPositions.compactMap { pendingPosition -> dydxPortfolioPendingPositionsItemViewModel? in + let item = Self.createPendingPositionsViewModelItem(pendingPosition: pendingPosition, + marketMap: marketMap, + assetMap: assetMap, + pendingPositionsCache: pendingPositionsCache) + pendingPositionsCache[pendingPosition.assetId] = item + return item + } + + self.viewModel?.pendingPositionItems = items + } + + static func s() { + } + + static func createPendingPositionsViewModelItem( + pendingPosition: SubaccountPendingPosition, + marketMap: [String: PerpetualMarket], + assetMap: [String: Asset], + pendingPositionsCache: [String: dydxPortfolioPendingPositionsItemViewModel]? = nil + ) -> dydxPortfolioPendingPositionsItemViewModel? { + + guard let market = marketMap[pendingPosition.marketId], + let configs = market.configs, + let asset = assetMap[pendingPosition.assetId], + let margin = pendingPosition.equity?.current?.doubleValue, + margin != 0, + let marginFormatted = dydxFormatter.shared.dollar(number: margin, digits: 2) + else { + return nil + } + + let viewOrdersAction: () -> Void = { + Router.shared?.navigate(to: RoutingRequest(path: "/market", + params: ["market": market.id, + "currentSection": "positions"]), + animated: true, + completion: nil) + } + let cancelOrdersAction: () -> Void = { + Router.shared?.navigate(to: RoutingRequest(path: "/trade/markets", params: ["market": market.id]), animated: true, completion: nil) + } + + return dydxPortfolioPendingPositionsItemViewModel(marketLogoUrl: URL(string: asset.resources?.imageUrl ?? ""), + marketName: asset.name!, + margin: marginFormatted, + orderCount: pendingPosition.orderCount, + viewOrdersAction: viewOrdersAction, + cancelOrdersAction: cancelOrdersAction) + } + + static func createPositionViewModelItem(position: SubaccountPosition, marketMap: [String: PerpetualMarket], assetMap: [String: Asset], positionsCache: [String: dydxPortfolioPositionItemViewModel]? = nil) -> dydxPortfolioPositionItemViewModel? { guard let market = marketMap[position.id], let configs = market.configs, let asset = assetMap[position.assetId], (position.size.current?.doubleValue ?? 0) != 0 else { return nil } - let item = cache?[position.assetId] ?? dydxPortfolioPositionItemViewModel() + let item = positionsCache?[position.assetId] ?? dydxPortfolioPositionItemViewModel() let positionSize = abs(position.size.current?.doubleValue ?? 0) item.size = dydxFormatter.shared.localFormatted(number: positionSize, digits: configs.displayStepSizeDecimals?.intValue ?? 1) diff --git a/dydx/dydxStateManager/dydxStateManager/AbacusState+Combine.swift b/dydx/dydxStateManager/dydxStateManager/AbacusState+Combine.swift index b5406a923..aadc83895 100644 --- a/dydx/dydxStateManager/dydxStateManager/AbacusState+Combine.swift +++ b/dydx/dydxStateManager/dydxStateManager/AbacusState+Combine.swift @@ -221,6 +221,17 @@ public final class AbacusState { .eraseToAnyPublisher() } + public var selectedSubaccountPendingPositions: AnyPublisher<[SubaccountPendingPosition], Never> { + selectedSubaccount + .compactMap { subaccount in + subaccount?.pendingPositions + } + .prepend([]) + .removeDuplicates() + .share() + .eraseToAnyPublisher() + } + public var selectedSubaccountOrders: AnyPublisher<[SubaccountOrder], Never> { selectedSubaccount .compactMap { subaccount in diff --git a/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj b/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj index ab7b82f7e..eb4109129 100644 --- a/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj +++ b/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj @@ -195,7 +195,7 @@ 27C027452AFD734800E92CCB /* dydxSettingsHelpRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C027442AFD734800E92CCB /* dydxSettingsHelpRowView.swift */; }; 27C6E4C92BC8C30E00ED892A /* dydxCustomLimitPriceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C6E4BC2BC8C30E00ED892A /* dydxCustomLimitPriceViewModel.swift */; }; 27CDA3D42BBF1AD700FEAFFE /* dydxMultipleOrdersExistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CDA3D32BBF1AD700FEAFFE /* dydxMultipleOrdersExistViewModel.swift */; }; - 27E072D22C1A095C0034B963 /* dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E072D12C1A095C0034B963 /* dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift */; }; + 27E072D22C1A095C0034B963 /* dydxPortfolioPendingPositionsItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E072D12C1A095C0034B963 /* dydxPortfolioPendingPositionsItemViewModel.swift */; }; 27ED340C2AD47CB100C159F5 /* dydxBannerErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27ED340B2AD47CB100C159F5 /* dydxBannerErrorAlert.swift */; }; 27ED365C2AD735A800C159F5 /* dydxSecurityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27ED365B2AD735A800C159F5 /* dydxSecurityView.swift */; }; 27F624112BBD9FEB00AB6D1A /* dydxPriceInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F624042BBD9FEB00AB6D1A /* dydxPriceInputViewModel.swift */; }; @@ -566,7 +566,7 @@ 27C027442AFD734800E92CCB /* dydxSettingsHelpRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSettingsHelpRowView.swift; sourceTree = ""; }; 27C6E4BC2BC8C30E00ED892A /* dydxCustomLimitPriceViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxCustomLimitPriceViewModel.swift; sourceTree = ""; }; 27CDA3D32BBF1AD700FEAFFE /* dydxMultipleOrdersExistViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxMultipleOrdersExistViewModel.swift; sourceTree = ""; }; - 27E072D12C1A095C0034B963 /* dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift; sourceTree = ""; }; + 27E072D12C1A095C0034B963 /* dydxPortfolioPendingPositionsItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxPortfolioPendingPositionsItemViewModel.swift; sourceTree = ""; }; 27ED340B2AD47CB100C159F5 /* dydxBannerErrorAlert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxBannerErrorAlert.swift; sourceTree = ""; }; 27ED365B2AD735A800C159F5 /* dydxSecurityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSecurityView.swift; sourceTree = ""; }; 27F624042BBD9FEB00AB6D1A /* dydxPriceInputViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxPriceInputViewModel.swift; sourceTree = ""; }; @@ -1143,7 +1143,7 @@ 02678FA329666BD800EE346B /* dydxPortfolioPositionsView.swift */, 02678FA529666BE600EE346B /* dydxPortfolioOrdersView.swift */, 024F488D2965C91D00E40247 /* dydxPortfolioChartView.swift */, - 27E072D12C1A095C0034B963 /* dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift */, + 27E072D12C1A095C0034B963 /* dydxPortfolioPendingPositionsItemViewModel.swift */, ); path = Sections; sourceTree = ""; @@ -2014,7 +2014,7 @@ 029CBE7728F608F400259C1D /* dydxMarketTradesView.swift in Sources */, 0238FC46296DA53F002E1C1A /* dydxOrderDetailsView.swift in Sources */, 2769090E2AAFD8030075B2D6 /* TransferInstanceViewModel.swift in Sources */, - 27E072D22C1A095C0034B963 /* dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift in Sources */, + 27E072D22C1A095C0034B963 /* dydxPortfolioPendingPositionsItemViewModel.swift in Sources */, 024FEB642ACB75E10087A55E /* dydxFeesStuctureView.swift in Sources */, 277E918B2B27762F005CCBCB /* dydxRewardsLaunchIncentivesView.swift in Sources */, 0268BBF92A8BE08C00D0C59B /* dydxTransferOutView.swift in Sources */, diff --git a/dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift b/dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/dydxPortfolioPendingPositionsItemViewModel.swift similarity index 77% rename from dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift rename to dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/dydxPortfolioPendingPositionsItemViewModel.swift index 483df291b..656c44335 100644 --- a/dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift +++ b/dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/dydxPortfolioPendingPositionsItemViewModel.swift @@ -1,5 +1,5 @@ // -// dydxPortfolioUnopenedIsolatedPositionsItemViewModel.swift +// dydxPortfolioPendingPositionsItemViewModel.swift // dydxUI // // Created by Michael Maguire on 6/12/24. @@ -8,30 +8,35 @@ import PlatformUI import SwiftUI +import Utilities -public class dydxPortfolioUnopenedIsolatedPositionsItemViewModel: PlatformViewModel { +public class dydxPortfolioPendingPositionsItemViewModel: PlatformViewModel { @Published public var marketLogoUrl: URL? @Published public var marketName: String @Published public var margin: String + @Published public var orderCount: Int32 @Published public var viewOrdersAction: (() -> Void) @Published public var cancelOrdersAction: (() -> Void) - public init(marketLogoUrl: URL? = nil, + public init(marketLogoUrl: URL?, marketName: String, margin: String, + orderCount: Int32, viewOrdersAction: @escaping () -> Void, cancelOrdersAction: @escaping () -> Void) { self.marketLogoUrl = marketLogoUrl self.marketName = marketName self.margin = margin + self.orderCount = orderCount self.viewOrdersAction = viewOrdersAction self.cancelOrdersAction = cancelOrdersAction } - public static var previewValue: dydxPortfolioUnopenedIsolatedPositionsItemViewModel = { + public static var previewValue: dydxPortfolioPendingPositionsItemViewModel = { .init(marketLogoUrl: URL(string: "https://v4.testnet.dydx.exchange/currencies/eth.png"), marketName: "ETH-USD", margin: "$1000.00", + orderCount: 2, viewOrdersAction: {}, cancelOrdersAction: {} ) @@ -63,12 +68,22 @@ public class dydxPortfolioUnopenedIsolatedPositionsItemViewModel: PlatformViewMo } private var divider: some View { - Divider() + Spacer(minLength: 1) .overlay(ThemeColor.SemanticColor.borderDefault.color) } private var bottomContent: some View { - let viewOrders = Text(localizerPathKey: "APP.CLOSE_POSITIONS_CONFIRMATION_TOAST.VIEW_ORDERS") + let viewOrdersStringKey: String + let viewOrdersStringParams: [String: String]? + if orderCount > 1 { + viewOrdersStringKey = "APP.GENERAL.VIEW_ORDER" + viewOrdersStringParams = nil + } else { + viewOrdersStringKey = "APP.GENERAL.VIEW_ORDERS_COUNT" + viewOrdersStringParams = ["NUM_ORDERS": "\(orderCount)"] + } + + let viewOrders = Text(localizerPathKey: viewOrdersStringKey, params: viewOrdersStringParams) .themeFont(fontSize: .smaller) .themeColor(foreground: .colorPurple) .padding(.vertical, 8) @@ -101,12 +116,12 @@ public class dydxPortfolioUnopenedIsolatedPositionsItemViewModel: PlatformViewMo } #if DEBUG -struct dydxPortfolioUnopenedIsolatedPositionsItemView_Previews: PreviewProvider { +struct dydxPortfolioPendingPositionsItemView_Previews: PreviewProvider { @StateObject static var themeSettings = ThemeSettings.shared static var previews: some View { Group { - dydxPortfolioUnopenedIsolatedPositionsItemViewModel.previewValue + dydxPortfolioPendingPositionsItemViewModel.previewValue .createView() .environmentObject(themeSettings) .previewLayout(.sizeThatFits) diff --git a/dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/dydxPortfolioPositionsView.swift b/dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/dydxPortfolioPositionsView.swift index b1b3a4655..adea59873 100644 --- a/dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/dydxPortfolioPositionsView.swift +++ b/dydx/dydxViews/dydxViews/_v4/Portfolio/Components/Sections/dydxPortfolioPositionsView.swift @@ -328,7 +328,7 @@ public class dydxPortfolioPositionsViewModel: PlatformViewModel { contentChanged?() } } - @Published public var unopenedIsolatedPositionItems: [dydxPortfolioUnopenedIsolatedPositionsItemViewModel] { + @Published public var pendingPositionItems: [dydxPortfolioPendingPositionsItemViewModel] { didSet { contentChanged?() } @@ -338,11 +338,11 @@ public class dydxPortfolioPositionsViewModel: PlatformViewModel { init( positionItems: [dydxPortfolioPositionItemViewModel] = [], - unopenedIsolatedPositionItems: [dydxPortfolioUnopenedIsolatedPositionsItemViewModel] = [], + pendingPositionItems: [dydxPortfolioPendingPositionsItemViewModel] = [], emptyText: String? = nil ) { self.positionItems = positionItems - self.unopenedIsolatedPositionItems = unopenedIsolatedPositionItems + self.pendingPositionItems = pendingPositionItems self.emptyText = emptyText } @@ -352,7 +352,7 @@ public class dydxPortfolioPositionsViewModel: PlatformViewModel { .previewValue, .previewValue ], - unopenedIsolatedPositionItems: [ + pendingPositionItems: [ .previewValue ], emptyText: "empty") @@ -389,15 +389,16 @@ public class dydxPortfolioPositionsViewModel: PlatformViewModel { .wrappedViewModel } - public var unopenedIsolatedPositionsHeader: PlatformViewModel? { - guard dydxBoolFeatureFlag.enable_isolated_margins.isEnabled == true, !unopenedIsolatedPositionItems.isEmpty else { return nil } + public var pendingPositionsHeader: PlatformViewModel? { + guard dydxBoolFeatureFlag.enable_isolated_margins.isEnabled == true, !pendingPositionItems.isEmpty else { return nil } return HStack(spacing: 8) { Text(localizerPathKey: "APP.TRADE.UNOPENED_ISOLATED_POSITIONS") .themeFont(fontSize: .larger) .themeColor(foreground: .textPrimary) .fixedSize() - Text("\(unopenedIsolatedPositionItems.count)") + Text("\(pendingPositionItems.count)") .frame(width: 28, height: 28) + .themeColor(background: .layer6) .borderAndClip(style: .circle, borderColor: .borderDefault) Spacer() } @@ -411,7 +412,7 @@ public class dydxPortfolioPositionsViewModel: PlatformViewModel { PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in guard let self = self else { return AnyView(PlatformView.nilView) } - if let emptyText = self.emptyText, positionItems.isEmpty, unopenedIsolatedPositionItems.isEmpty { + if let emptyText = self.emptyText, positionItems.isEmpty, pendingPositionItems.isEmpty { return AnyView( PlaceholderViewModel(text: emptyText) .createView(parentStyle: style) @@ -419,22 +420,28 @@ public class dydxPortfolioPositionsViewModel: PlatformViewModel { } let items = self.positionItems.map { $0.createView(parentStyle: style) } - let unopenedItems = self.unopenedIsolatedPositionItems.map { $0.createView(parentStyle: style) } + let unopenedItems = self.pendingPositionItems.map { $0.createView(parentStyle: style) } return AnyView( ScrollView { - LazyVStack { - self.positionsHeader?.createView(parentStyle: style) + VStack(spacing: 24) { + LazyVStack { + self.positionsHeader?.createView(parentStyle: style) - ForEach(items.indices, id: \.self) { index in - items[index] - } + ForEach(items.indices, id: \.self) { index in + items[index] + } - self.positionsFooter?.createView(parentStyle: style) - self.unopenedIsolatedPositionsHeader?.createView(parentStyle: style) + self.positionsFooter?.createView(parentStyle: style) + } + if dydxBoolFeatureFlag.enable_isolated_margins.isEnabled { + LazyVStack { + self.pendingPositionsHeader?.createView(parentStyle: style) - ForEach(unopenedItems.indices, id: \.self) { index in - unopenedItems[index] + ForEach(unopenedItems.indices, id: \.self) { index in + unopenedItems[index] + } + } } } }