From 958f09bed2cd861522d4c8be7b15a1b3f7825a56 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 5 Aug 2024 12:28:57 -0400 Subject: [PATCH 1/4] add chart radio buttons --- .../Theme/ThemeViewModifiers.swift | 19 +-- .../dydxViews.xcodeproj/project.pbxproj | 4 + .../Landing/dydxVaultChartViewModel.swift | 151 ++++++++++++++++++ .../Vault/Landing/dydxVaultViewModel.swift | 7 +- 4 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift index b07b4dbb6..6a8e1f42b 100644 --- a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift @@ -291,13 +291,13 @@ public extension View { modifier(BorderModifier(cornerRadius: cornerRadius, borderWidth: borderWidth, borderColor: borderColor)) } - func borderAndClip(style: BorderAndClipStyle, borderColor: ThemeColor.SemanticColor, lineWidth: CGFloat = 1) -> some View { + func borderAndClip(style: ClipStyle, borderColor: ThemeColor.SemanticColor, lineWidth: CGFloat = 1) -> some View { modifier(BorderAndClipModifier(style: style, borderColor: borderColor, lineWidth: lineWidth)) } } /// The clip shape/style -public enum BorderAndClipStyle { +public enum ClipStyle { /// A rectangular shape with rounded corners with specified corner radius, aligned inside the frame of the view containing it. case cornerRadius(CGFloat) /// A capsule shape is equivalent to a rounded rectangle where the corner radius is chosen as half the length of the rectangle’s smallest edge. @@ -306,7 +306,7 @@ public enum BorderAndClipStyle { } private struct BorderAndClipModifier: ViewModifier { - let style: BorderAndClipStyle + let style: ClipStyle let borderColor: ThemeColor.SemanticColor let lineWidth: CGFloat @@ -453,14 +453,15 @@ public extension View { private struct CenterAlignedModifier: ViewModifier { func body(content: Content) -> some View { - HStack { - Spacer() - VStack { - Spacer() + // forcing minLength to 0 does make a difference, default behavior is black box + HStack(spacing: 0) { + Spacer(minLength: 0) + VStack(spacing: 0) { + Spacer(minLength: 0) content - Spacer() + Spacer(minLength: 0) } - Spacer() + Spacer(minLength: 0) } } } diff --git a/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj b/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj index fecbbe350..52f8e47aa 100644 --- a/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj +++ b/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ 02FD2B8B292307A200A5609E /* LeverageRiskChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FD2B8A292307A200A5609E /* LeverageRiskChange.swift */; }; 02FF0BCE29AE928700781EDA /* dydxWalletListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FF0BCD29AE928700781EDA /* dydxWalletListView.swift */; }; 02FF0BD529AEB91900781EDA /* dydxWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FF0BD429AEB91900781EDA /* dydxWalletView.swift */; }; + 2700A3172C5D72BB00880AFA /* dydxVaultChartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2700A3162C5D72BB00880AFA /* dydxVaultChartViewModel.swift */; }; 27044F7F2BBB1D5A004C750D /* dydxTakeProfitStopLossViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27044F7E2BBB1D5A004C750D /* dydxTakeProfitStopLossViewModel.swift */; }; 270BA8CF2A6F1470009212EA /* dydxDebugThemeViewerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 270BA8CE2A6F1470009212EA /* dydxDebugThemeViewerView.swift */; }; 270E7E342A5F700B00136793 /* dydxOrderbookManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 270E7E332A5F700B00136793 /* dydxOrderbookManagerView.swift */; }; @@ -536,6 +537,7 @@ 02FD2B8A292307A200A5609E /* LeverageRiskChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeverageRiskChange.swift; sourceTree = ""; }; 02FF0BCD29AE928700781EDA /* dydxWalletListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxWalletListView.swift; sourceTree = ""; }; 02FF0BD429AEB91900781EDA /* dydxWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxWalletView.swift; sourceTree = ""; }; + 2700A3162C5D72BB00880AFA /* dydxVaultChartViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxVaultChartViewModel.swift; sourceTree = ""; }; 27044F7E2BBB1D5A004C750D /* dydxTakeProfitStopLossViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTakeProfitStopLossViewModel.swift; sourceTree = ""; }; 270BA8CE2A6F1470009212EA /* dydxDebugThemeViewerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxDebugThemeViewerView.swift; sourceTree = ""; }; 270E7E332A5F700B00136793 /* dydxOrderbookManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxOrderbookManagerView.swift; sourceTree = ""; }; @@ -1488,6 +1490,7 @@ isa = PBXGroup; children = ( 2751D6542C59646700B36F95 /* dydxVaultViewModel.swift */, + 2700A3162C5D72BB00880AFA /* dydxVaultChartViewModel.swift */, ); path = Landing; sourceTree = ""; @@ -2016,6 +2019,7 @@ 022EDC8E29A048B3003D59A7 /* dydxClosePositionHeaderView.swift in Sources */, 27E0736B2C20D27F0034B963 /* dydxCancelPendingIsolatedOrdersView.swift in Sources */, 02F1D3882BEAA6CA00A9376C /* dydxTradeInputMarginView.swift in Sources */, + 2700A3172C5D72BB00880AFA /* dydxVaultChartViewModel.swift in Sources */, 0256F53629AFFC9800A083C0 /* dydxOnboardConnectView.swift in Sources */, 024F48902965CE9200E40247 /* dydxPortfolioDetailsView.swift in Sources */, 02A8976028E6A962006F1658 /* dydxMarketAssetListView.swift in Sources */, diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift new file mode 100644 index 000000000..c245de3f8 --- /dev/null +++ b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift @@ -0,0 +1,151 @@ +// +// dydxVaultChartViewModel.swift +// dydxViews +// +// Created by Michael Maguire on 8/2/24. +// + +import Foundation +import PlatformUI +import SwiftUI +import Utilities + +private protocol RadioButtonContentDisplayable: Equatable { + var displayText: String { get } +} + +public class dydxVaultChartViewModel: PlatformViewModel { + @Published var selectedValueType: ValueTypeOption = .pnl + @Published var selectedValueTime: ValueTimeOption = .oneDay + + fileprivate let valueTypeOptions = ValueTypeOption.allCases + fileprivate let valueTimeOptions = ValueTimeOption.allCases + + public enum ValueTypeOption: CaseIterable, RadioButtonContentDisplayable { + case pnl + case equity + + var displayText: String { + let path: String + switch self { + case .pnl: + path = "APP.VAULTS.VAULT_PNL" + case .equity: + path = "APP.VAULTS.VAULT_EQUITY" + } + return DataLocalizer.shared?.localize(path: path, params: nil) ?? "" + } + } + + public enum ValueTimeOption: CaseIterable, RadioButtonContentDisplayable { + case oneDay + case sevenDays + case thirtyDays + + var displayText: String { + let path: String + switch self { + case .oneDay: + path = "1d" + case .sevenDays: + path = "7d" + case .thirtyDays: + path = "30d" + } + return path + } + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] _ in + guard let self = self else { return AnyView(PlatformView.nilView) } + return AnyView(dydxVaultChartView(viewModel: self)).wrappedInAnyView() + } + } +} + +private struct dydxVaultChartView: View { + @ObservedObject var viewModel: dydxVaultChartViewModel + + var body: some View { + HStack(spacing: 0) { + RadioButtonGroup(selected: $viewModel.selectedValueType, + options: viewModel.valueTypeOptions, + buttonClipStyle: .capsule, + itemWidth: nil, + itemHeight: 40 + ) + Spacer() + RadioButtonGroup(selected: $viewModel.selectedValueTime, + options: viewModel.valueTimeOptions, + buttonClipStyle: .circle, + itemWidth: 40, + itemHeight: 40 + ) + } + + } +} + +fileprivate struct RadioButtonGroup: View { + + @Binding fileprivate var selected: Content + + fileprivate let options: [Content] + + fileprivate let buttonClipStyle: ClipStyle + fileprivate let itemWidth: CGFloat? + fileprivate let itemHeight: CGFloat? + + var body: some View { + HStack(spacing: 8) { + ForEach(0.. Void + + private var verticalSpacer: some View { + Spacer(minLength: 11) + } + + private var horizontalSpacer: some View { + Spacer(minLength: 8) + } + + var body: some View { + Text(displayText) + .lineLimit(1) + .themeColor(foreground: isSelected ? .textPrimary : .textTertiary) + .themeFont(fontType: .base, fontSize: .smaller) + // if width is specified, i.e. non-nil, setting horizontal inset to 0 will allow entire space to be used horizontally + .padding(.horizontal, width == nil ? 8 : 0) + // if height is specified, i.e. non-nil, setting vertical inset to 0 will allow entire space to be used horizontally + .padding(.horizontal, width == nil ? 8 : 0) + .centerAligned() + .frame(width: width, height: height) + .fixedSize() + .themeColor(background: isSelected ? .layer1 : .layer3) + .borderAndClip(style: clipStyle, borderColor: .borderDefault) + .onTapGesture { + selectionAction() + } + } +} diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift index f58e64d6c..4c384b1ed 100644 --- a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift +++ b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift @@ -42,6 +42,8 @@ private struct dydxVaultView: View { aprTvlRow Spacer().frame(height: 16) div + Spacer().frame(height: 16) + chart Spacer() } @@ -175,5 +177,8 @@ private struct dydxVaultView: View { } // MARK: - Section 3 - graph - + var chart: some View { + dydxVaultChartViewModel() + .createView() + } } From 4ba664d8ceef1c49cb40d7d0a587a0f63bf18b17 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 7 Aug 2024 11:24:37 -0400 Subject: [PATCH 2/4] fine tune graph --- .../DesignSystem/Theme/ThemeConfig.swift | 2 +- .../_Graphing/DateTimeAxisFormatter.swift | 1 + .../dydxViews.xcodeproj/project.pbxproj | 12 ++ .../Landing/RadioButtons/RadioButtons.swift | 80 +++++++ .../Landing/dydxVaultChartViewModel.swift | 196 ++++++++++++------ .../Vault/Landing/dydxVaultViewModel.swift | 1 + 6 files changed, 223 insertions(+), 69 deletions(-) create mode 100644 dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeConfig.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeConfig.swift index 30b997898..2f0fdb34c 100644 --- a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeConfig.swift +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeConfig.swift @@ -381,7 +381,7 @@ public struct FontTypeDetail: Codable, Equatable { public extension ThemeFont { - private func uiFont(of fontType: FontType, fontSize: FontSize) -> UIFont? { + func uiFont(of fontType: FontType, fontSize: FontSize) -> UIFont? { let sizeValue: Float switch fontSize { case .custom(size: let size): diff --git a/dydx/dydxChart/dydxChart/_Graphing/DateTimeAxisFormatter.swift b/dydx/dydxChart/dydxChart/_Graphing/DateTimeAxisFormatter.swift index b41c81005..119a22ae6 100644 --- a/dydx/dydxChart/dydxChart/_Graphing/DateTimeAxisFormatter.swift +++ b/dydx/dydxChart/dydxChart/_Graphing/DateTimeAxisFormatter.swift @@ -120,6 +120,7 @@ import Foundation open func stringForValue(_ value: Double, axis _: AxisBase?) -> String { var result: String = "" if let anchor = GraphingAnchor.shared?.date { + // what is 2500 here? let time = anchor.addingTimeInterval(unitInterval() * (Double(Int(value)) - 2500.0)) switch resolution { case .unknown: diff --git a/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj b/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj index 52f8e47aa..0425a3aaa 100644 --- a/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj +++ b/dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj @@ -196,6 +196,7 @@ 27AFE4EA2C1902BF00168B0B /* dydxReceiptChangeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27AFE4DD2C1902BF00168B0B /* dydxReceiptChangeItemView.swift */; }; 27BAAF112BD851B500F650C3 /* dydxTakeProfitStopLossStatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27BAAF102BD851B500F650C3 /* dydxTakeProfitStopLossStatusViewModel.swift */; }; 27C027452AFD734800E92CCB /* dydxSettingsHelpRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C027442AFD734800E92CCB /* dydxSettingsHelpRowView.swift */; }; + 27C6AE182C618055005517B5 /* RadioButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C6AE172C618055005517B5 /* RadioButtons.swift */; }; 27C6E4C92BC8C30E00ED892A /* dydxCustomLimitPriceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C6E4BC2BC8C30E00ED892A /* dydxCustomLimitPriceViewModel.swift */; }; 27CDA3D42BBF1AD700FEAFFE /* dydxMultipleOrdersExistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CDA3D32BBF1AD700FEAFFE /* dydxMultipleOrdersExistViewModel.swift */; }; 27DBF3C92C4A05B9009EB2D6 /* dydxTitledNumberField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27DBF3C82C4A05B9009EB2D6 /* dydxTitledNumberField.swift */; }; @@ -572,6 +573,7 @@ 27AFE4DD2C1902BF00168B0B /* dydxReceiptChangeItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxReceiptChangeItemView.swift; sourceTree = ""; }; 27BAAF102BD851B500F650C3 /* dydxTakeProfitStopLossStatusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTakeProfitStopLossStatusViewModel.swift; sourceTree = ""; }; 27C027442AFD734800E92CCB /* dydxSettingsHelpRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSettingsHelpRowView.swift; sourceTree = ""; }; + 27C6AE172C618055005517B5 /* RadioButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtons.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 = ""; }; 27DBF3C82C4A05B9009EB2D6 /* dydxTitledNumberField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTitledNumberField.swift; sourceTree = ""; }; @@ -1489,6 +1491,7 @@ 2751D6762C597BEC00B36F95 /* Landing */ = { isa = PBXGroup; children = ( + 27C6AE0A2C618044005517B5 /* RadioButtons */, 2751D6542C59646700B36F95 /* dydxVaultViewModel.swift */, 2700A3162C5D72BB00880AFA /* dydxVaultChartViewModel.swift */, ); @@ -1543,6 +1546,14 @@ path = Banner; sourceTree = ""; }; + 27C6AE0A2C618044005517B5 /* RadioButtons */ = { + isa = PBXGroup; + children = ( + 27C6AE172C618055005517B5 /* RadioButtons.swift */, + ); + path = RadioButtons; + sourceTree = ""; + }; 27D276222C519C98002775F2 /* dydxComponents */ = { isa = PBXGroup; children = ( @@ -2197,6 +2208,7 @@ 0230377328C13BBB00412B72 /* dydxMarketsView.swift in Sources */, 02F958142A1BDEE500828F9A /* dydxKeyExportView.swift in Sources */, 0238B90B29FB38F200FCE4D0 /* dydxThemeLoader.swift in Sources */, + 27C6AE182C618055005517B5 /* RadioButtons.swift in Sources */, 0279DE872BED3F5400F9ECF8 /* dydxAdjustMarginDirectionView.swift in Sources */, 023DC88F29CCBD2C000DD920 /* dydxOnboardQRCodeView.swift in Sources */, 0238FE08296EF8E5002E1C1A /* dydxMarketPositionView.swift in Sources */, diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift new file mode 100644 index 000000000..64c7ce0c8 --- /dev/null +++ b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift @@ -0,0 +1,80 @@ +// +// RadioButtonGroup.swift +// dydxViews +// +// Created by Michael Maguire on 8/5/24. +// + +import Foundation +import SwiftUI +import PlatformUI + +protocol RadioButtonContentDisplayable: Equatable { + var displayText: String { get } +} + +/// A simplified version 2 of TabGroup. Use TabGroup when the radio buttons are not displaying exclusively Text. +struct RadioButtonGroup: View { + + @Binding var selected: ButtonItem + + let options: [ButtonItem] + + let buttonClipStyle: ClipStyle + /// when not specified, width will be natural. When specified, width will be forced + let itemWidth: CGFloat? + /// when not specified, height will be natural. When specified, height will be forced + let itemHeight: CGFloat? + + var body: some View { + HStack(spacing: 8) { + ForEach(0.. Void + + private var verticalSpacer: some View { + Spacer(minLength: 11) + } + + private var horizontalSpacer: some View { + Spacer(minLength: 8) + } + + var body: some View { + Text(displayText) + .lineLimit(1) + .themeColor(foreground: isSelected ? .textPrimary : .textTertiary) + .themeFont(fontType: .base, fontSize: .smaller) + // if width is specified, i.e. non-nil, setting horizontal inset to 0 will allow entire space to be used horizontally + .padding(.horizontal, width == nil ? 8 : 0) + // if height is specified, i.e. non-nil, setting vertical inset to 0 will allow entire space to be used horizontally + .padding(.horizontal, width == nil ? 8 : 0) + .centerAligned() + .frame(width: width, height: height) + .fixedSize() + .themeColor(background: isSelected ? .layer1 : .layer3) + .borderAndClip(style: clipStyle, borderColor: .borderDefault) + .onTapGesture { + selectionAction() + } + } +} diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift index c245de3f8..906ad2fd4 100644 --- a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift +++ b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultChartViewModel.swift @@ -9,18 +9,111 @@ import Foundation import PlatformUI import SwiftUI import Utilities +import Charts +import Combine +import dydxChart -private protocol RadioButtonContentDisplayable: Equatable { - var displayText: String { get } -} public class dydxVaultChartViewModel: PlatformViewModel { @Published var selectedValueType: ValueTypeOption = .pnl - @Published var selectedValueTime: ValueTimeOption = .oneDay + @Published var selectedValueTime: ValueTimeOption = .oneDay { + didSet { + //TODO: remove, just for testing + guard oldValue != selectedValueTime else { return } + setEntries(selectedValueTime: selectedValueTime, selectedValueType: selectedValueType) + } + } fileprivate let valueTypeOptions = ValueTypeOption.allCases fileprivate let valueTimeOptions = ValueTimeOption.allCases + fileprivate let lineChart = { + let lineChart = LineChartView() + lineChart.data = LineChartData() + lineChart.xAxis.drawGridLinesEnabled = false + lineChart.leftAxis.enabled = false + lineChart.rightAxis.enabled = false + lineChart.xAxis.drawGridLinesEnabled = false + lineChart.xAxis.drawAxisLineEnabled = false + lineChart.xAxis.labelPosition = .bottom + lineChart.xAxis.setLabelCount(5, force: true) + lineChart.xAxis.granularityEnabled = true + lineChart.xAxis.labelTextColor = ThemeColor.SemanticColor.textTertiary.uiColor + lineChart.xAxis.labelFont = ThemeSettings.shared.themeConfig.themeFont.uiFont(of: .number, fontSize: .smallest) ?? .systemFont(ofSize: 11) + lineChart.xAxis.valueFormatter = ValueTimeOption.oneDay.valueFormatter + lineChart.xAxis.avoidFirstLastClippingEnabled = true + // default is 5, but then would have to add that offset to ViewPortOffsets's bottom + lineChart.xAxis.yOffset = 0 + // enables edge-to-edge + lineChart.setViewPortOffsets(left: 0, top: 0, right: 0, bottom: lineChart.xAxis.labelFont.lineHeight * 2) + lineChart.pinchZoomEnabled = false + lineChart.doubleTapToZoomEnabled = false + // enables dragging the highlighted value indicator + lineChart.dragEnabled = true + lineChart.legend.enabled = false + + return lineChart + }() + + // TODO: replace with actual data, delete cancellables + public func setEntries(entries: [ChartDataEntry] = [], selectedValueTime newSelectedValueTime: ValueTimeOption? = nil, selectedValueType newSelectedValueType: ValueTypeOption? = nil) { + if let newSelectedValueType { + selectedValueType = newSelectedValueType + } + if let newSelectedValueTime { + selectedValueTime = newSelectedValueTime + } + //TODO: remove + // this is just for testing + let now = Date().timeIntervalSince1970 + let finalTimeSecondsAway = selectedValueTime == .oneDay ? 3600.0*24.0 : selectedValueTime == .sevenDays ? 3600.0*24.0*7.0 : 3600.0*24.0*30.0 + let numEntries = Int.random(in: 0..<100) + let entries = (0..= (entries.first?.y ?? -Double.infinity) + let color = isPositive ? ThemeSettings.positiveColor.uiColor : ThemeSettings.negativeColor.uiColor + let gradientColors = [ + color.withAlphaComponent(0).cgColor, + color.withAlphaComponent(1).cgColor] + let gradient = CGGradient(colorsSpace: nil, colors: gradientColors as CFArray, locations: nil)! + + //colors + dataSet.fill = .fillWithLinearGradient(gradient, angle: 90) + dataSet.highlightColor = color + dataSet.setColor(color) + dataSet.drawFilledEnabled = true + + //shapes + dataSet.lineWidth = 3 + dataSet.lineCapType = .round + dataSet.mode = .linear + dataSet.label = nil + dataSet.drawCirclesEnabled = false + dataSet.drawValuesEnabled = false + + // interactions + dataSet.highlightEnabled = true + dataSet.drawHorizontalHighlightIndicatorEnabled = false + + lineChart.xAxis.valueFormatter = selectedValueTime.valueFormatter + + lineChart.data = LineChartData(dataSet: dataSet) + } + + // TODO: delete and replace with real data + private var cancellables = Set() + init() { + super.init() + Timer.publish(every: 1, triggerNow: true) + .sink { [weak self] _ in + self?.setEntries() + } + .store(in: &cancellables) + } + public enum ValueTypeOption: CaseIterable, RadioButtonContentDisplayable { case pnl case equity @@ -46,13 +139,24 @@ public class dydxVaultChartViewModel: PlatformViewModel { let path: String switch self { case .oneDay: - path = "1d" + path = "APP.GENERAL.TIME_STRINGS.1D" case .sevenDays: - path = "7d" + path = "APP.GENERAL.TIME_STRINGS.7D" case .thirtyDays: - path = "30d" + path = "APP.GENERAL.TIME_STRINGS._30D" } - return path + return DataLocalizer.shared?.localize(path: path, params: nil) ?? "" + } + + fileprivate var valueFormatter: TimeAxisValueFormatter { + let formatter = TimeAxisValueFormatter() + switch self { + case .oneDay: + formatter.dateFormat = "HH:mm" + case .sevenDays, .thirtyDays: + formatter.dateFormat = "HH:mm\nMM/dd" + } + return formatter } } @@ -67,85 +171,41 @@ public class dydxVaultChartViewModel: PlatformViewModel { private struct dydxVaultChartView: View { @ObservedObject var viewModel: dydxVaultChartViewModel - var body: some View { + private var radioButtonsRow: some View { HStack(spacing: 0) { - RadioButtonGroup(selected: $viewModel.selectedValueType, + RadioButtonGroup(selected: $viewModel.selectedValueType, options: viewModel.valueTypeOptions, buttonClipStyle: .capsule, itemWidth: nil, itemHeight: 40 ) Spacer() - RadioButtonGroup(selected: $viewModel.selectedValueTime, + RadioButtonGroup(selected: $viewModel.selectedValueTime, options: viewModel.valueTimeOptions, buttonClipStyle: .circle, itemWidth: 40, itemHeight: 40 ) } - + .padding(.horizontal, 12) + } + + private var chart: some View { + viewModel.lineChart.swiftUIView } -} - -fileprivate struct RadioButtonGroup: View { - @Binding fileprivate var selected: Content - - fileprivate let options: [Content] - - fileprivate let buttonClipStyle: ClipStyle - fileprivate let itemWidth: CGFloat? - fileprivate let itemHeight: CGFloat? - var body: some View { - HStack(spacing: 8) { - ForEach(0.. Void - - private var verticalSpacer: some View { - Spacer(minLength: 11) - } - - private var horizontalSpacer: some View { - Spacer(minLength: 8) - } - - var body: some View { - Text(displayText) - .lineLimit(1) - .themeColor(foreground: isSelected ? .textPrimary : .textTertiary) - .themeFont(fontType: .base, fontSize: .smaller) - // if width is specified, i.e. non-nil, setting horizontal inset to 0 will allow entire space to be used horizontally - .padding(.horizontal, width == nil ? 8 : 0) - // if height is specified, i.e. non-nil, setting vertical inset to 0 will allow entire space to be used horizontally - .padding(.horizontal, width == nil ? 8 : 0) - .centerAligned() - .frame(width: width, height: height) - .fixedSize() - .themeColor(background: isSelected ? .layer1 : .layer3) - .borderAndClip(style: clipStyle, borderColor: .borderDefault) - .onTapGesture { - selectionAction() - } +// DateTimeAxisFormatter is broken for ONEHOUR and seem to overcomplicate time value formatting so writing custom IAxisValueFormatter +fileprivate class TimeAxisValueFormatter: DateFormatter, IAxisValueFormatter { + func stringForValue(_ value: Double, axis: AxisBase?) -> String { + let date = Date(timeIntervalSince1970: value) + return self.string(from: date) } } diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift index 4c384b1ed..843ea69c7 100644 --- a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift +++ b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/dydxVaultViewModel.swift @@ -180,5 +180,6 @@ private struct dydxVaultView: View { var chart: some View { dydxVaultChartViewModel() .createView() + .frame(height: 174) } } From 39d835bf66e94e6df1a604c09646742efbdc9747 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 7 Aug 2024 11:28:17 -0400 Subject: [PATCH 3/4] comment --- .../_v4/Vault/Landing/RadioButtons/RadioButtons.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift index 64c7ce0c8..94c339388 100644 --- a/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift +++ b/dydx/dydxViews/dydxViews/_v4/Vault/Landing/RadioButtons/RadioButtons.swift @@ -13,7 +13,8 @@ protocol RadioButtonContentDisplayable: Equatable { var displayText: String { get } } -/// A simplified version 2 of TabGroup. Use TabGroup when the radio buttons are not displaying exclusively Text. +/// A simplified version of TabGroup which supports binding for the selected option and does not require a view builder for each item. +/// Use TabGroup when the radio buttons are not displaying exclusively Text. struct RadioButtonGroup: View { @Binding var selected: ButtonItem From bdc7d99df0b2c2d6efe6bfe0aab0879ca9b29428 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 7 Aug 2024 12:15:32 -0400 Subject: [PATCH 4/4] comment --- .../PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift index 6a8e1f42b..698f233ba 100644 --- a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift @@ -453,7 +453,7 @@ public extension View { private struct CenterAlignedModifier: ViewModifier { func body(content: Content) -> some View { - // forcing minLength to 0 does make a difference, default behavior is black box + // forcing minLength to 0 does make a difference, default behavior is black box behavior which seems to vary HStack(spacing: 0) { Spacer(minLength: 0) VStack(spacing: 0) {