Skip to content

Commit

Permalink
CLI-628: vault landing screen positions list (#234)
Browse files Browse the repository at this point in the history
* add sections to vault position view

* scrolling touchups

* keep chart vm in memory

* polish

* remove unnecessary chart notifications

---------

Co-authored-by: Mike <[email protected]>
  • Loading branch information
mike-dydx and mike-dydx authored Aug 21, 2024
1 parent dd6b0f7 commit c224399
Show file tree
Hide file tree
Showing 8 changed files with 392 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import PlatformParticles
import RoutingKit
import ParticlesKit
import PlatformUI
import Charts

public class dydxVaultViewBuilder: NSObject, ObjectBuilderProtocol {
public func build<T>() -> T? {
let presenter = dydxVaultViewBuilderPresenter()
let view = presenter.viewModel?.createView() ?? PlatformViewModel().createView()
return dydxVaultViewController(presenter: presenter, view: view, configuration: .default) as? T
return dydxVaultViewController(presenter: presenter, view: view, configuration: .tabbarItemView) as? T
// return HostingViewController(presenter: presenter, view: view) as? T
}
}
Expand All @@ -41,5 +42,45 @@ private class dydxVaultViewBuilderPresenter: HostedViewPresenter<dydxVaultViewMo
super.init()

viewModel = dydxVaultViewModel()
viewModel?.vaultChart = dydxVaultChartViewModel()

//TODO: remove & replace, test only
Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] _ in
guard let self = self else { return }
self.viewModel?.vaultChart?.setEntries(entries: self.generateEntries())
self.viewModel?.positions = self.generatePositions()
}
}

// TODO: remove, just for testing
private func generateEntries() -> [ChartDataEntry] {
let selectedValueTime = viewModel?.vaultChart?.selectedValueTime ?? .oneDay
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..<numEntries).map { i in
ChartDataEntry(x: now + Double(i)/Double(numEntries) * finalTimeSecondsAway, y: Double.random(in: 0..<100))
}
return entries
}

//TODO: remove
// this is just for testing
private func generatePositions() -> [dydxVaultPositionViewModel] {
return [
dydxVaultPositionViewModel(assetName: "logo_bitcoin", market: "BTC", side: .long, leverage: 10.80, notionalValue: 100000, positionSize: 10000, token: "BTC", tokenUnitPrecision: 6, pnlAmount: 1000, pnlPercentage: 10, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_ethereum", market: "ETH", side: .short, leverage: 88.88, notionalValue: 50000, positionSize: 10000, token: "ETH", tokenUnitPrecision: -1, pnlAmount: -500, pnlPercentage: -1, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_bitcoin", market: "BTC", side: .long, leverage: 10.80, notionalValue: 100000, positionSize: 10000, token: "BTC", tokenUnitPrecision: 6, pnlAmount: 1000, pnlPercentage: 10, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_ethereum", market: "ETH", side: .short, leverage: 88.88, notionalValue: 50000, positionSize: 10000, token: "ETH", tokenUnitPrecision: -1, pnlAmount: -500, pnlPercentage: -1, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_bitcoin", market: "BTC", side: .long, leverage: 10.80, notionalValue: 100000, positionSize: 10000, token: "BTC", tokenUnitPrecision: 6, pnlAmount: 1000, pnlPercentage: 10, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_ethereum", market: "ETH", side: .short, leverage: 88.88, notionalValue: 50000, positionSize: 10000, token: "ETH", tokenUnitPrecision: -1, pnlAmount: -500, pnlPercentage: -1, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_bitcoin", market: "BTC", side: .long, leverage: 10.80, notionalValue: 100000, positionSize: 10000, token: "BTC", tokenUnitPrecision: 6, pnlAmount: 1000, pnlPercentage: 10, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_ethereum", market: "ETH", side: .short, leverage: 88.88, notionalValue: 50000, positionSize: 10000, token: "ETH", tokenUnitPrecision: -1, pnlAmount: -500, pnlPercentage: -1, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_bitcoin", market: "BTC", side: .long, leverage: 10.80, notionalValue: 100000, positionSize: 10000, token: "BTC", tokenUnitPrecision: 6, pnlAmount: 1000, pnlPercentage: 10, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_ethereum", market: "ETH", side: .short, leverage: 88.88, notionalValue: 50000, positionSize: 10000, token: "ETH", tokenUnitPrecision: -1, pnlAmount: -500, pnlPercentage: -1, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_bitcoin", market: "BTC", side: .long, leverage: 10.80, notionalValue: 100000, positionSize: 10000, token: "BTC", tokenUnitPrecision: 6, pnlAmount: 1000, pnlPercentage: 10, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
dydxVaultPositionViewModel(assetName: "logo_ethereum", market: "ETH", side: .short, leverage: 88.88, notionalValue: 50000, positionSize: 10000, token: "ETH", tokenUnitPrecision: -1, pnlAmount: -500, pnlPercentage: -1, sparklineValues: (0..<10).map { _ in Double.random(in: 0.0...1.0) }),
]

}
}
8 changes: 8 additions & 0 deletions dydx/dydxViews/dydxViews.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@
27DBF3C92C4A05B9009EB2D6 /* dydxTitledNumberField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27DBF3C82C4A05B9009EB2D6 /* dydxTitledNumberField.swift */; };
27E072D22C1A095C0034B963 /* dydxPortfolioPendingPositionsItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E072D12C1A095C0034B963 /* dydxPortfolioPendingPositionsItemViewModel.swift */; };
27E0736B2C20D27F0034B963 /* dydxCancelPendingIsolatedOrdersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E0736A2C20D27F0034B963 /* dydxCancelPendingIsolatedOrdersView.swift */; };
27EB25832C6D1E5E008C187B /* dydxVaultPositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27EB25822C6D1E5E008C187B /* dydxVaultPositionView.swift */; };
27EB25852C6D28BF008C187B /* SparklineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27EB25842C6D28BF008C187B /* SparklineChart.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 */; };
Expand Down Expand Up @@ -583,6 +585,8 @@
27DBF3C82C4A05B9009EB2D6 /* dydxTitledNumberField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxTitledNumberField.swift; sourceTree = "<group>"; };
27E072D12C1A095C0034B963 /* dydxPortfolioPendingPositionsItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxPortfolioPendingPositionsItemViewModel.swift; sourceTree = "<group>"; };
27E0736A2C20D27F0034B963 /* dydxCancelPendingIsolatedOrdersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxCancelPendingIsolatedOrdersView.swift; sourceTree = "<group>"; };
27EB25822C6D1E5E008C187B /* dydxVaultPositionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxVaultPositionView.swift; sourceTree = "<group>"; };
27EB25842C6D28BF008C187B /* SparklineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparklineChart.swift; sourceTree = "<group>"; };
27ED340B2AD47CB100C159F5 /* dydxBannerErrorAlert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxBannerErrorAlert.swift; sourceTree = "<group>"; };
27ED365B2AD735A800C159F5 /* dydxSecurityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dydxSecurityView.swift; sourceTree = "<group>"; };
27F624042BBD9FEB00AB6D1A /* dydxPriceInputViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = dydxPriceInputViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -938,6 +942,7 @@
0273A15E2ACCDDB1001B89F5 /* SelectionBar.swift */,
273F50152B7C3F120034792A /* SignedAmountView.swift */,
27D276222C519C98002775F2 /* dydxComponents */,
27EB25842C6D28BF008C187B /* SparklineChart.swift */,
);
path = Shared;
sourceTree = "<group>";
Expand Down Expand Up @@ -1500,6 +1505,7 @@
27C6AE0A2C618044005517B5 /* RadioButtons */,
2751D6542C59646700B36F95 /* dydxVaultViewModel.swift */,
2700A3162C5D72BB00880AFA /* dydxVaultChartViewModel.swift */,
27EB25822C6D1E5E008C187B /* dydxVaultPositionView.swift */,
);
path = Landing;
sourceTree = "<group>";
Expand Down Expand Up @@ -2066,6 +2072,7 @@
0279DE892BED402C00F9ECF8 /* dydxAdjustMarginPercentageView.swift in Sources */,
026382EB28F0EF0000F766FA /* dydxMarketPriceCandlesResolutionsView.swift in Sources */,
02F99F3E29E4D5750009B3E8 /* dydxTransferSearchItemView.swift in Sources */,
27EB25832C6D1E5E008C187B /* dydxVaultPositionView.swift in Sources */,
27DBF3C92C4A05B9009EB2D6 /* dydxTitledNumberField.swift in Sources */,
0284201629AD71B600C0E7CC /* Enums.swift in Sources */,
02678FA629666BE600EE346B /* dydxPortfolioOrdersView.swift in Sources */,
Expand Down Expand Up @@ -2098,6 +2105,7 @@
277E918B2B27762F005CCBCB /* dydxRewardsLaunchIncentivesView.swift in Sources */,
0268BBF92A8BE08C00D0C59B /* dydxTransferOutView.swift in Sources */,
0279DE452BEBE75100F9ECF8 /* dydxTargetLeverageCtaButtonView.swift in Sources */,
27EB25852C6D28BF008C187B /* SparklineChart.swift in Sources */,
0208627A28F4D95F00C9D3A0 /* dydxMarketInfoPagingView.swift in Sources */,
027F3EF72AB93ADC00602E5B /* dydxProfileBalancesViewModel.swift in Sources */,
024B44F52983E38D00E35D54 /* dydxTradeStatusLogoView.swift in Sources */,
Expand Down
11 changes: 11 additions & 0 deletions dydx/dydxViews/dydxViews/Shared/SideText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ public class SideTextViewModel: PlatformViewModel, Hashable {
return ""
}
}

var color: ThemeColor.SemanticColor {
switch self {
case .long, .buy:
return ThemeSettings.positiveColor
case .short, .sell:
return ThemeSettings.negativeColor
case .custom, .none:
return ThemeColor.SemanticColor.textPrimary
}
}

public init(positionSide: PositionSide) {
switch positionSide {
Expand Down
57 changes: 57 additions & 0 deletions dydx/dydxViews/dydxViews/Shared/SparklineChart.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// SparklineChart.swift
// dydxViews
//
// Created by Michael Maguire on 8/14/24.
//

import Foundation
import SwiftUI
import Charts
import PlatformUI

struct SparklineView: View {
@State var values: [Double]

private var lineChart: some View {
let chart = LineChartView()
chart.data = LineChartData()
chart.xAxis.drawGridLinesEnabled = false
chart.leftAxis.enabled = false
chart.rightAxis.enabled = false
chart.xAxis.enabled = false
chart.setViewPortOffsets(left: 0, top: 0, right: 0, bottom: 0)
chart.pinchZoomEnabled = false
chart.doubleTapToZoomEnabled = false
// enables dragging the highlighted value indicator
chart.dragEnabled = false
chart.legend.enabled = false

let entries = (0..<values.count).map { ChartDataEntry(x: Double($0), y: values[$0]) }
let dataSet = LineChartDataSet(entries: entries)
let isPositive = (entries.last?.y ?? -Double.infinity) >= (entries.first?.y ?? -Double.infinity)
let color = isPositive ? ThemeSettings.positiveColor.uiColor : ThemeSettings.negativeColor.uiColor

//colors
dataSet.setColor(color)

//shapes
dataSet.lineWidth = 1.5
dataSet.lineCapType = .round
dataSet.mode = .linear
dataSet.label = nil
dataSet.drawCirclesEnabled = false
dataSet.drawValuesEnabled = false

// interactions
dataSet.highlightEnabled = false
dataSet.drawHorizontalHighlightIndicatorEnabled = false

chart.data = LineChartData(dataSet: dataSet)
return chart.swiftUIView
}

var body: some View {
lineChart
}
}
2 changes: 2 additions & 0 deletions dydx/dydxViews/dydxViews/Shared/TokenText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class TokenTextViewModel: PlatformViewModel, Hashable {
return AnyView(
Group {
Text(self.symbol)
.lineLimit(1)
.minimumScaleFactor(0.5)
.padding(.vertical, 1)
.padding(.horizontal, 3)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,12 @@ import dydxChart


public class dydxVaultChartViewModel: PlatformViewModel {
@Published var selectedValueType: ValueTypeOption = .pnl
@Published var selectedValueTime: ValueTimeOption = .oneDay {
didSet {
//TODO: remove, just for testing
guard oldValue != selectedValueTime else { return }
setEntries(selectedValueTime: selectedValueTime, selectedValueType: selectedValueType)
}
}
@Published public var selectedValueType: ValueTypeOption = .pnl
@Published public var selectedValueTime: ValueTimeOption = .oneDay

fileprivate let valueTypeOptions = ValueTypeOption.allCases
fileprivate let valueTimeOptions = ValueTimeOption.allCases

fileprivate let lineChart = {
let lineChart = LineChartView()
lineChart.data = LineChartData()
Expand Down Expand Up @@ -55,23 +49,10 @@ public class dydxVaultChartViewModel: PlatformViewModel {
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..<numEntries).map { i in
ChartDataEntry(x: now + Double(i)/Double(numEntries) * finalTimeSecondsAway, y: Double.random(in: 0..<100))
}

public init() {}

// TODO: replace with actual data
public func setEntries(entries: [ChartDataEntry] = []) {
let dataSet = LineChartDataSet(entries: entries)
let isPositive = (entries.last?.y ?? -Double.infinity) >= (entries.first?.y ?? -Double.infinity)
let color = isPositive ? ThemeSettings.positiveColor.uiColor : ThemeSettings.negativeColor.uiColor
Expand Down Expand Up @@ -102,17 +83,6 @@ public class dydxVaultChartViewModel: PlatformViewModel {

lineChart.data = LineChartData(dataSet: dataSet)
}

// TODO: delete and replace with real data
private var cancellables = Set<AnyCancellable>()
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
Expand Down
Loading

0 comments on commit c224399

Please sign in to comment.