diff --git a/ios/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj b/ios/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj index d6139052..eb0c61a4 100644 --- a/ios/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj +++ b/ios/dydx/dydxPresenters/dydxPresenters.xcodeproj/project.pbxproj @@ -87,7 +87,6 @@ 0258BA23299294BF0098E1BE /* dydxProfileViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0258BA22299294BF0098E1BE /* dydxProfileViewBuilder.swift */; }; 0258BA2929929E870098E1BE /* dydxProfileButtonsViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0258BA2829929E870098E1BE /* dydxProfileButtonsViewPresenter.swift */; }; 025D04162E31E7620015E1D7 /* dydxTurnkeyAuthViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025D04152E31E7610015E1D7 /* dydxTurnkeyAuthViewBuilder.swift */; }; - 025D22D628F65E1B00C4ADAE /* dydxMarketStatsViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025D22D528F65E1B00C4ADAE /* dydxMarketStatsViewPresenter.swift */; }; 0262EFA629D79EE4009889E2 /* Wallets2ViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0262EF9929D79EE4009889E2 /* Wallets2ViewBuilder.swift */; }; 0262F2D529DB4891009889E2 /* WalletAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0262F2D429DB4891009889E2 /* WalletAction.swift */; }; 02669B7C2AD8661F00A756AA /* dydxCarteraConfigWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02669B7B2AD8661F00A756AA /* dydxCarteraConfigWorker.swift */; }; @@ -576,7 +575,6 @@ 0258BA22299294BF0098E1BE /* dydxProfileViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxProfileViewBuilder.swift; sourceTree = ""; }; 0258BA2829929E870098E1BE /* dydxProfileButtonsViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxProfileButtonsViewPresenter.swift; sourceTree = ""; }; 025D04152E31E7610015E1D7 /* dydxTurnkeyAuthViewBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTurnkeyAuthViewBuilder.swift; sourceTree = ""; tabWidth = 3; }; - 025D22D528F65E1B00C4ADAE /* dydxMarketStatsViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxMarketStatsViewPresenter.swift; sourceTree = ""; }; 0262EF9929D79EE4009889E2 /* Wallets2ViewBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wallets2ViewBuilder.swift; sourceTree = ""; }; 0262F2D429DB4891009889E2 /* WalletAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletAction.swift; sourceTree = ""; }; 02669B7B2AD8661F00A756AA /* dydxCarteraConfigWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxCarteraConfigWorker.swift; sourceTree = ""; }; @@ -1362,7 +1360,6 @@ 029CBE7828F6092400259C1D /* dydxMarketTradesViewPresenter.swift */, 024FEB402ACB60690087A55E /* dydxMarketOrderbookPresenter.swift */, 0208627B28F4DAC000C9D3A0 /* dydxMarketInfoPagingViewPresenter.swift */, - 025D22D528F65E1B00C4ADAE /* dydxMarketStatsViewPresenter.swift */, 024F47912964A83700E40247 /* dydxMarketConfigsViewPresenter.swift */, 02048E262D398D7C00394CBE /* dydxMarketTpSlGroupViewPresenter.swift */, 0238FE09296EF91D002E1C1A /* dydxMarketPositionViewPresenter.swift */, @@ -2536,7 +2533,6 @@ 02A651892DB6DA22006DBD5F /* dydxSimpleUIMenuButtonsViewPresenter.swift in Sources */, 020494732D4433CE00394CBE /* dydxFirstTimeViewBuilder.swift in Sources */, 02D6DB272D124AAF008AAEA1 /* dydxAppModeViewBuilder.swift in Sources */, - 025D22D628F65E1B00C4ADAE /* dydxMarketStatsViewPresenter.swift in Sources */, 6453AB2B299EA4380041A0C4 /* dydxClosePositionInputCtaButtonViewPresenter.swift in Sources */, 023DC88029CCBD21000DD920 /* dydxOnboardQRCodeViewBuilder.swift in Sources */, 27351D452AC4A67900E4A563 /* dydxRestrictionsWorker.swift in Sources */, diff --git a/ios/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/Components/dydxMarketStatsViewPresenter.swift b/ios/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/Components/dydxMarketStatsViewPresenter.swift deleted file mode 100644 index 6dbed432..00000000 --- a/ios/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/Components/dydxMarketStatsViewPresenter.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// dydxMarketStatsViewPresenter.swift -// dydxPresenters -// -// Created by Rui Huang on 10/11/22. -// - -import Utilities -import dydxViews -import PlatformParticles -import RoutingKit -import ParticlesKit -import PlatformUI -import Combine -import dydxStateManager -import Abacus -import dydxFormatter -import SwiftUI - -protocol dydxMarketStatsViewPresenterProtocol: HostedViewPresenterProtocol { - var viewModel: dydxMarketStatsViewModel? { get } -} - -class dydxMarketStatsViewPresenter: HostedViewPresenter, dydxMarketStatsViewPresenterProtocol { - @Published var marketId: String? - - override init() { - super.init() - - viewModel = dydxMarketStatsViewModel() - } - - override func start() { - super.start() - - Publishers - .CombineLatest3($marketId, - AbacusStateManager.shared.state.marketMap, - AbacusStateManager.shared.state.assetMap) - .sink { [weak self] marketId, marketMap, assetMap in - guard let marketId = marketId, let market = marketMap[marketId] else { return } - let tickSizeNumDecimals = market.configs?.displayTickSizeDecimals?.intValue ?? 0 - let stepSizeNumDecimals = market.configs?.displayStepSizeDecimals?.intValue ?? 0 - self?.updateStats(market: market, asset: assetMap[market.assetId], stepSizeNumDecimals: stepSizeNumDecimals, tickSizeNumDecimals: tickSizeNumDecimals) - } - .store(in: &subscriptions) - } - - private func updateStats(market: PerpetualMarket, asset: Asset?, stepSizeNumDecimals: Int, tickSizeNumDecimals: Int) { - var items = [dydxMarketStatsViewModel.StatItem]() - - let oraclePrice = dydxFormatter.shared.dollar(number: market.oraclePrice?.doubleValue, digits: tickSizeNumDecimals) ?? "-" - items += [ - .init(header: DataLocalizer.localize(path: "APP.TRADE.ORACLE_PRICE"), - value: SignedAmountViewModel(text: oraclePrice, sign: .none, coloringOption: .allText)) - ] - - let volume = dydxFormatter.shared.dollarVolume(number: market.perpetual?.volume24H) ?? "-" - items += [ - .init(header: DataLocalizer.localize(path: "APP.TRADE.VOLUME_24H"), - value: SignedAmountViewModel(text: volume, sign: .none, coloringOption: .allText)) - ] - - let change: String - if let value = market.priceChange24HPercent?.doubleValue { - change = dydxFormatter.shared.percent(number: abs(value), digits: 2) ?? "-" - } else { - change = "-" - } - var sign = PlatformUISign(value: market.priceChange24HPercent?.doubleValue) - items += [ - .init(header: DataLocalizer.localize(path: "APP.TRADE.CHANGE_24H"), - value: SignedAmountViewModel(text: change, sign: sign, coloringOption: .allText)) - ] - - let fundingRate: String - if let value = market.perpetual?.nextFundingRate?.doubleValue { - fundingRate = dydxFormatter.shared.percent(number: abs(value), digits: 6) ?? "-" - } else { - fundingRate = "-" - } - sign = PlatformUISign(value: market.perpetual?.nextFundingRate?.doubleValue) - items += [ - .init(header: DataLocalizer.localize(path: "APP.TRADE.FUNDING_RATE"), - value: SignedAmountViewModel(text: fundingRate, sign: sign, coloringOption: .allText)) - ] - - let nextFundingViewModel: PlatformViewModel - if let nextFundingAtMilliseconds = market.perpetual?.nextFundingAtMilliseconds { - let nextFundingAt = Date(milliseconds: nextFundingAtMilliseconds.doubleValue) - nextFundingViewModel = IntervalTextModel(date: nextFundingAt, direction: .countDown, format: .full) - } else { - // With no nextFundingAt, we will just count down to the next hour mark - nextFundingViewModel = IntervalTextModel(date: nil, direction: .countDownToHour, format: .full) - } - - items += [ - .init(header: DataLocalizer.localize(path: "APP.TRADE.NEXT_FUNDING"), - value: nextFundingViewModel) - ] - - let openInterest = dydxFormatter.shared.localFormatted(number: market.perpetual?.openInterest, digits: stepSizeNumDecimals) ?? "-" - let token: TokenTextViewModel? - if let symbol = asset?.displayableAssetId { - token = TokenTextViewModel(symbol: symbol) - } else { - token = nil - } - items += [ - .init(header: DataLocalizer.localize(path: "APP.TRADE.OPEN_INTEREST"), - value: SignedAmountViewModel(text: openInterest, sign: .none, coloringOption: .allText), - token: token) - ] - - viewModel?.statItems = items - } -} diff --git a/ios/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/dydxMarketInfoViewBuilder.swift b/ios/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/dydxMarketInfoViewBuilder.swift index ddff3945..65294bf3 100644 --- a/ios/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/dydxMarketInfoViewBuilder.swift +++ b/ios/dydx/dydxPresenters/dydxPresenters/_v4/MarketInfo/dydxMarketInfoViewBuilder.swift @@ -62,7 +62,6 @@ private class dydxMarketInfoViewPresenter: HostedViewPresenter Bool { - false // always reload - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(header) - hasher.combine(token) - } - - public init(header: String, value: PlatformViewModel, token: TokenTextViewModel? = nil) { - self.header = header - self.value = value - self.token = token - } - - let header: String - let value: PlatformViewModel - let token: TokenTextViewModel? - } - - @Published private var statRows = [[StatItem]]() - - @Published public var statItems: [StatItem] = [] { - didSet { - var newItems = [[StatItem]]() - var currentRow = [StatItem]() - for item in statItems { - currentRow.append(item) - if currentRow.count == 2 { - newItems.append(currentRow) - currentRow = [] - } - } - if currentRow.count > 0 { - newItems.append(currentRow) - } - statRows = newItems - } - } - - public init() { } - - public static var previewValue: dydxMarketStatsViewModel = { - let vm = dydxMarketStatsViewModel() - vm.statItems = [ - .init(header: "Index Price", value: SignedAmountViewModel.previewValue), - .init(header: "Oracle Price", value: SignedAmountViewModel.previewValue), - .init(header: "12h Volume", value: SignedAmountViewModel.previewValue, token: .previewValue) - ] - 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( - VStack(spacing: 0) { - DividerModel().createView(parentStyle: style) - - ForEach(self.statRows, id: \.self) { row in - HStack(spacing: 0) { - ForEach(row, id: \.self) { item in - VStack { - HStack { - Text(item.header) - .themeFont(fontSize: .smaller) - .themeColor(foreground: .textTertiary) - Spacer() - } - Spacer() - HStack { - item.value - .createView(parentStyle: style) - item.token? - .createView(parentStyle: style.themeFont(fontSize: .smaller)) - - Spacer() - } - .minimumScaleFactor(0.5) - } - .padding(.horizontal, 16) - .padding(.vertical, 16) - .frame(maxWidth: .infinity) - } - - DividerModel() - .createView(parentStyle: style) - .frame(maxHeight: .infinity) - } - .frame(height: 78) - - DividerModel().createView(parentStyle: style) - } - } - ) - } - } -} - -#if DEBUG -struct dydxMarketStatsView_Previews_Dark: PreviewProvider { - @StateObject static var themeSettings = ThemeSettings.shared - - static var previews: some View { - ThemeSettings.applyDarkTheme() - ThemeSettings.applyStyles() - return dydxMarketStatsViewModel.previewValue - .createView() - // .edgesIgnoringSafeArea(.bottom) - .previewLayout(.sizeThatFits) - } -} - -struct dydxMarketStatsView_Previews_Light: PreviewProvider { - @StateObject static var themeSettings = ThemeSettings.shared - - static var previews: some View { - ThemeSettings.applyLightTheme() - ThemeSettings.applyStyles() - return dydxMarketStatsViewModel.previewValue - .createView() - // .edgesIgnoringSafeArea(.bottom) - .previewLayout(.sizeThatFits) - } -} -#endif diff --git a/ios/dydx/dydxViews/dydxViews/_v4/MarketInfo/Components/dydxMarketInfoHeaderView.swift b/ios/dydx/dydxViews/dydxViews/_v4/MarketInfo/Components/dydxMarketInfoHeaderView.swift index 1d1b8dce..65466e00 100644 --- a/ios/dydx/dydxViews/dydxViews/_v4/MarketInfo/Components/dydxMarketInfoHeaderView.swift +++ b/ios/dydx/dydxViews/dydxViews/_v4/MarketInfo/Components/dydxMarketInfoHeaderView.swift @@ -26,66 +26,97 @@ public class dydxMarketInfoHeaderViewModel: PlatformViewModel { }() private func createMarketSelectorView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> some View { - HStack(spacing: 15) { - HStack(spacing: 12) { - PlatformIconViewModel(type: .url(url: self.sharedMarketViewModel?.logoUrl), - clip: .defaultCircle, - size: CGSize(width: 40, height: 40), - backgroundColor: .colorWhite) - .createView(parentStyle: parentStyle, styleKey: styleKey) - VStack(alignment: .leading, spacing: 0) { - HStack(spacing: 4) { - Text(sharedMarketViewModel?.assetName ?? "") - .themeColor(foreground: .textSecondary) - .themeFont(fontType: .base, fontSize: .large) - .lineLimit(1) - .minimumScaleFactor(0.5) - TokenTextViewModel(symbol: sharedMarketViewModel?.assetId ?? "") - .createView(parentStyle: parentStyle.themeFont(fontSize: .smallest), styleKey: styleKey) - } - HStack(alignment: .center, spacing: 4) { - Text(sharedMarketViewModel?.indexPrice ?? "") - .themeColor(foreground: .textPrimary) - .themeFont(fontType: .number, fontSize: .large) - .lineLimit(1) - .minimumScaleFactor(0.5) - sharedMarketViewModel?.priceChangePercent24H? - .createView(parentStyle: parentStyle.themeFont(fontSize: .medium), styleKey: styleKey) - } - } + HStack(spacing: 8) { + PlatformIconViewModel(type: .url(url: self.sharedMarketViewModel?.logoUrl), + clip: .defaultCircle, + size: CGSize(width: 24, height: 24), + backgroundColor: .colorWhite) + .createView(parentStyle: parentStyle, styleKey: styleKey) + HStack(spacing: 4) { + Text(sharedMarketViewModel?.assetName ?? "") + .themeColor(foreground: .textSecondary) + .themeFont(fontType: .base, fontSize: .large) + .lineLimit(1) + .minimumScaleFactor(0.5) } PlatformIconViewModel(type: .asset(name: "icon_dropdown", bundle: .dydxView), clip: .noClip, size: .init(width: 14, height: 8)) .createView(parentStyle: parentStyle, styleKey: styleKey) } - .padding(.horizontal, 12) - .padding(.vertical, 8) - .themeColor(background: .layer3) - .borderAndClip(style: .cornerRadius(12), borderColor: .borderDefault, lineWidth: 1) .onTapGesture(perform: self.onMarketSelectorTap ?? {}) } + private func statLine(labelKey: String, valueType: ThemeFont.FontType? = .base, value: () -> Value) -> some View { + VStack(alignment: .leading, spacing: 2) { + Text(DataLocalizer.localize(path: labelKey)) + .themeColor(foreground: .textTertiary) + .themeFont(fontType: .base, fontSize: .smallest) + .lineLimit(1) + .minimumScaleFactor(0.5) + value() + .themeColor(foreground: .textSecondary) + .themeFont(fontType: valueType, fontSize: .medium) + .lineLimit(1) + .minimumScaleFactor(0.5) + } + } + + private func statLine(labelKey: String, value: String, valueType: ThemeFont.FontType? = .base) -> some View { + statLine(labelKey: labelKey) { Text(value) } + } + + private func createMarketInfoView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> some View { + HStack(alignment: .center) { + statLine(labelKey: "APP.GENERAL.PRICE", value: sharedMarketViewModel?.indexPrice ?? "") + .padding(.horizontal, 16) + .overlay( + Rectangle() + .frame(width: 1) + .themeColor(foreground: .layer3), + alignment: .trailing + ) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 20) { + statLine(labelKey: "APP.GENERAL.MARKET_CAP", value: sharedMarketViewModel?.marketCap ?? "") + statLine(labelKey: "APP.TRADE.CHANGE_24H") { sharedMarketViewModel?.priceChangePercent24H?.createView() } + statLine(labelKey: "APP.TRADE.VOLUME_24H", value: sharedMarketViewModel?.volume24H ?? "") + statLine(labelKey: "APP.TRADE.OPEN_INTEREST", value: sharedMarketViewModel?.openInterest ?? "") + statLine(labelKey: "APP.TRADE.FUNDING_RATE_SHORT") { sharedMarketViewModel?.fundingRate?.createView() } + statLine(labelKey: "APP.TRADE.NEXT_FUNDING") { sharedMarketViewModel?.nextFunding?.createView() } + statLine(labelKey: "APP.GENERAL.BUYING_POWER", value: sharedMarketViewModel?.buyingPower ?? "") + } + .padding(.horizontal, 16) + } + } + .padding(.vertical, 4) + } + 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, self.sharedMarketViewModel != nil else { return AnyView(PlatformView.nilView) } - return HStack(spacing: 16) { - ChevronBackButtonModel(onBackButtonTap: self.onBackButtonTap ?? {}) - .createView(parentStyle: style) - .frame(width: 32) + return VStack { + HStack(spacing: 16) { + ChevronBackButtonModel(onBackButtonTap: self.onBackButtonTap ?? {}) + .createView(parentStyle: style) + .frame(width: 32) - self.createMarketSelectorView(parentStyle: parentStyle, styleKey: styleKey) - .frame(maxWidth: .infinity) + self.createMarketSelectorView(parentStyle: parentStyle, styleKey: styleKey) + .frame(maxWidth: .infinity) - self.favoriteViewModel?.createView(parentStyle: style) - .frame(width: 32) + self.favoriteViewModel?.createView(parentStyle: style) + .frame(width: 32) + } + .frame(maxWidth: .infinity) + .padding(.horizontal, 12) + DividerModel().createView() + self.createMarketInfoView(parentStyle: parentStyle) + DividerModel().createView() } - .frame(maxWidth: .infinity) - .padding(.horizontal, 12) - .wrappedInAnyView() + .wrappedInAnyView() } } } diff --git a/ios/dydx/dydxViews/dydxViews/_v4/MarketInfo/dydxMarketInfoView.swift b/ios/dydx/dydxViews/dydxViews/_v4/MarketInfo/dydxMarketInfoView.swift index 0924f9e3..2b485d51 100644 --- a/ios/dydx/dydxViews/dydxViews/_v4/MarketInfo/dydxMarketInfoView.swift +++ b/ios/dydx/dydxViews/dydxViews/_v4/MarketInfo/dydxMarketInfoView.swift @@ -13,7 +13,6 @@ import Utilities public class dydxMarketInfoViewModel: PlatformViewModel { @Published public var header = dydxMarketInfoHeaderViewModel() @Published public var paging: dydxMarketInfoPagingViewModel? = dydxMarketInfoPagingViewModel() - @Published public var stats: dydxMarketStatsViewModel? = dydxMarketStatsViewModel() @Published public var resources = dydxMarketResourcesViewModel() @Published public var configs: dydxMarketConfigsViewModel? = dydxMarketConfigsViewModel() @@ -44,7 +43,6 @@ public class dydxMarketInfoViewModel: PlatformViewModel { let vm = dydxMarketInfoViewModel() vm.header = .previewValue vm.paging = .previewValue - vm.stats = .previewValue vm.resources = .previewValue vm.configs = .previewValue vm.sections = .previewValue @@ -71,8 +69,6 @@ public class dydxMarketInfoViewModel: PlatformViewModel { self.createPositionSection(parentStyle: style) Spacer(minLength: 24) - self.createStatsSection(parentStyle: style) - self.createDetailsSection(parentStyle: style) self.createConfigsSection(parentStyle: style) @@ -128,13 +124,6 @@ public class dydxMarketInfoViewModel: PlatformViewModel { } } - private func createStatsSection(parentStyle: ThemeStyle) -> some View { - stats? - .createView(parentStyle: parentStyle) - .frame(width: UIScreen.main.bounds.width) - .sectionHeader(path: "APP.GENERAL.STATISTICS") - } - private func createDetailsSection(parentStyle: ThemeStyle) -> some View { resources .createView(parentStyle: parentStyle)