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

MOB-601 : Position Card Updates #206

Merged
merged 6 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,30 @@ extension Abacus.NotificationType {
}

final class dydxAlertsWorker: BaseWorker {
private var handledAlertHashes = Set<Int>()
static let userDefaultsKey: String = "dydxAlertsWorker"

// do not set directly, use `markAlertAsHandled` instead
private var handledAlertIds = {
if let handledAlertIds = UserDefaults.standard.array(forKey: dydxAlertsWorker.userDefaultsKey) as? [String] {
return Set(handledAlertIds)
} else {
return Set<String>()
}
}()

private func markAlertAsHandled(_ alert: Abacus.Notification) {
if handledAlertIds.insert(alert.betterId).inserted {
var handledAlerts = Array(handledAlertIds)
handledAlerts.append(alert.betterId)
UserDefaults.standard.set(handledAlerts, forKey: dydxAlertsWorker.userDefaultsKey)
}
}

override func start() {
super.start()

AbacusStateManager.shared.state.alerts
.removeDuplicates()
.receive(on: RunLoop.main)
.sink { [weak self] alerts in
self?.updateAlerts(alerts: alerts)
}
Expand All @@ -49,7 +65,7 @@ final class dydxAlertsWorker: BaseWorker {
private func updateAlerts(alerts: [Abacus.Notification]) {
alerts
// don't display an alert which has already been handled
.filter { !handledAlertHashes.contains($0.hashValue) }
.filter { !handledAlertIds.contains($0.betterId) }
// display alerts in chronological order they were received
.sorted { $0.updateTimeInMilliseconds < $1.updateTimeInMilliseconds }
.forEach { alert in
Expand All @@ -58,13 +74,22 @@ final class dydxAlertsWorker: BaseWorker {
Router.shared?.navigate(to: RoutingRequest(path: link!), animated: true, completion: nil)
}] : nil
if SettingsStore.shared?.shouldDisplayInAppNotifications != false {
ErrorInfo.shared?.info(title: alert.title,
message: alert.text,
type: alert.type.infoType,
error: nil, time: nil, actions: actions)
DispatchQueue.main.async {
ErrorInfo.shared?.info(title: alert.title,
message: alert.text,
type: alert.type.infoType,
error: nil, time: nil, actions: actions)
}
}
// add to alert ids set to avoid double handling
handledAlertHashes.insert(alert.hashValue)
markAlertAsHandled(alert)
}
}
}

private extension Abacus.Notification {
// id is just the fill id which is not guaranteed unique
var betterId: String {
"\(self.id)\(self.updateTimeInMilliseconds)\(self.title)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ class dydxPortfolioPositionsViewPresenter: HostedViewPresenter<dydxPortfolioPosi
let item = positionsCache?[position.assetId] ?? dydxPortfolioPositionItemViewModel()

let positionSize = abs(position.size.current?.doubleValue ?? 0)
let notionalValue = abs(position.valueTotal.current?.doubleValue ?? 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm i'm using notionalTotal here, what is valueTotal?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

val valueTotal = size * oraclePrice
set(valueTotal, modified, "valueTotal", period)
val notional = valueTotal.abs()
set(notional, modified, "notionalTotal", period)

i think we want notionalTotal here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm good catch, i was looking for notionalTotal earlier but it wasn't autopopulating addressed in ba3fd6c

item.size = dydxFormatter.shared.localFormatted(number: positionSize, digits: configs.displayStepSizeDecimals?.intValue ?? 1)
item.notionalValue = dydxFormatter.shared.dollar(number: notionalValue, digits: 2) ?? "--"
item.token?.symbol = asset.id

if position.resources.indicator.current == "long" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,16 @@ final class dydxTradeReceiptPresenter: dydxReceiptPresenter {
AbacusStateManager.shared.state.selectedSubaccountPositions,
AbacusStateManager.shared.state.marketMap)
.sink { [weak self] input, positions, marketMap in
if let tradeSummary = input.summary,
let marketId = input.marketId,
let market = marketMap[marketId],
let position = positions.first(where: { $0.id == marketId }) {
self?.updateExpectedPrice(tradeSummary: tradeSummary, market: market)
self?.updateLiquidationPrice(position: position, market: market)
self?.updatePositionMargin(position: position)
self?.updatePositionLeverage(position: position)
self?.updateTradingFee(tradeSummary: tradeSummary)
self?.updateTradingRewards(tradeSummary: tradeSummary)
}
let tradeSummary = input.summary
let marketId = input.marketId
let market = marketMap[marketId ?? ""]
let position = positions.first(where: { $0.id == marketId })
self?.updateExpectedPrice(tradeSummary: tradeSummary, market: market)
self?.updateLiquidationPrice(position: position, market: market)
self?.updatePositionMargin(position: position)
self?.updatePositionLeverage(position: position)
self?.updateTradingFee(tradeSummary: tradeSummary)
self?.updateTradingRewards(tradeSummary: tradeSummary)
Comment on lines +46 to +55
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change ensures the initial (empty) state loads

despite null values, we still want to display the receipt line titles

}
.store(in: &subscriptions)

Expand Down Expand Up @@ -85,16 +84,16 @@ final class dydxTradeReceiptPresenter: dydxReceiptPresenter {
}
}

private func updateExpectedPrice(tradeSummary: TradeInputSummary?, market: PerpetualMarket) {
let value = dydxFormatter.shared.dollar(number: tradeSummary?.price?.doubleValue, digits: market.configs?.displayTickSizeDecimals?.intValue ?? 0)
private func updateExpectedPrice(tradeSummary: TradeInputSummary?, market: PerpetualMarket?) {
let value = dydxFormatter.shared.dollar(number: tradeSummary?.price?.doubleValue, digits: market?.configs?.displayTickSizeDecimals?.intValue ?? 2)
expectedPriceViewModel.title = DataLocalizer.localize(path: "APP.TRADE.EXPECTED_PRICE")
expectedPriceViewModel.value = value
}

private func updateLiquidationPrice(position: SubaccountPosition?, market: PerpetualMarket) {
private func updateLiquidationPrice(position: SubaccountPosition?, market: PerpetualMarket?) {
let title = DataLocalizer.localize(path: "APP.TRADE.LIQUIDATION_PRICE_SHORT")
let unit = AmountTextModel.Unit.dollar
let tickSize = market.configs?.displayTickSizeDecimals?.intValue.asNsNumber
let tickSize = market?.configs?.displayTickSizeDecimals?.intValue.asNsNumber ?? 2
liquidationPriceViewModel.title = title
liquidationPriceViewModel.value = createAmountChangeViewModel(title: title, tradeState: position?.liquidationPrice, tickSize: tickSize, unit: unit)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class dydxPortfolioPositionItemViewModel: PlatformViewModel {
}

public init(size: String? = nil,
notionalValue: String? = nil,
token: TokenTextViewModel? = TokenTextViewModel(),
sideText: SideTextViewModel = SideTextViewModel(),
leverage: String? = nil,
Expand All @@ -38,6 +39,7 @@ public class dydxPortfolioPositionItemViewModel: PlatformViewModel {
onTapAction: (() -> Void)? = nil,
onMarginEditAction: (() -> Void)? = nil) {
self.size = size
self.notionalValue = notionalValue
self.token = token
self.sideText = sideText
self.leverage = leverage
Expand All @@ -51,6 +53,7 @@ public class dydxPortfolioPositionItemViewModel: PlatformViewModel {
}

@Published public var size: String?
@Published public var notionalValue: String?
@Published public var token: TokenTextViewModel?
@Published public var sideText = SideTextViewModel()
@Published public var leverage: String?
Expand All @@ -69,6 +72,7 @@ public class dydxPortfolioPositionItemViewModel: PlatformViewModel {
public static var previewValue: dydxPortfolioPositionItemViewModel {
let item = dydxPortfolioPositionItemViewModel(
size: "299",
notionalValue: "$420.69",
token: .previewValue,
sideText: .previewValue,
leverage: "0.01x",
Expand All @@ -94,13 +98,12 @@ public class dydxPortfolioPositionItemViewModel: PlatformViewModel {
}

return AnyView(
VStack {
VStack(spacing: 20) {
self.createTopView(parentStyle: style)
self.createBottomView(parentStyle: style)
}
.frame(height: 120)
.padding(.vertical, 12)
.padding(.horizontal, 8)
.padding(.vertical, 16)
.padding(.horizontal, 20)
.themeGradient(background: .layer3, gradientType: self.gradientType)
.cornerRadius(16)
.onTapGesture { [weak self] in
Expand All @@ -112,89 +115,90 @@ public class dydxPortfolioPositionItemViewModel: PlatformViewModel {
}

private func createTopView(parentStyle: ThemeStyle) -> some View {
let icon = self.createLogo(parentStyle: parentStyle)
let main = self.createMain(parentStyle: parentStyle)

return PlatformTableViewCellViewModel(logo: icon.wrappedViewModel,
main: main.wrappedViewModel)
.createView(parentStyle: parentStyle)
HStack(spacing: 0) {
createLogo(parentStyle: parentStyle)
Spacer(minLength: 8)
createTopRowStats(parentStyle: parentStyle)
}
}

private func createBottomView(parentStyle: ThemeStyle) -> some View {
GeometryReader { geo in
HStack(alignment: .top) {
SingleAxisGeometryReader(axis: .horizontal, alignment: .center) { width in
let numElements: CGFloat = 3.0
let spacing: CGFloat = 8
let elementWidth = max(0, (width - (numElements - 1) * spacing) / numElements)
return HStack(alignment: .top, spacing: 8) {
VStack(alignment: .leading, spacing: 4) {
Text(DataLocalizer.localize(path: "APP.GENERAL.INDEX_ENTRY"))
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)

Text(self.indexPrice ?? "")
.themeFont(fontSize: .small)
.themeColor(foreground: .textPrimary)
.minimumScaleFactor(0.5)

Text(self.entryPrice ?? "")
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)
.minimumScaleFactor(0.5)
Group {
Text(DataLocalizer.localize(path: "APP.GENERAL.INDEX_ENTRY"))
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)

Text(self.indexPrice ?? "")
.themeFont(fontSize: .small)
.themeColor(foreground: .textPrimary)

Text(self.entryPrice ?? "")
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)
}
.lineLimit(1)
.minimumScaleFactor(0.5)
.frame(width: elementWidth, alignment: .leading)
}
.leftAligned()
.frame(width: geo.size.width / 3)

VStack(alignment: .leading, spacing: 4) {
Text(DataLocalizer.localize(path: "APP.GENERAL.PROFIT_AND_LOSS"))
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)

self.unrealizedPnl?.createView(parentStyle: parentStyle.themeFont(fontType: .number, fontSize: .small))
Text(self.unrealizedPnlPercent)
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)
Group {
Text(DataLocalizer.localize(path: "APP.GENERAL.PROFIT_AND_LOSS"))
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)

self.unrealizedPnl?.createView(parentStyle: parentStyle.themeFont(fontType: .number, fontSize: .small))
Text(self.unrealizedPnlPercent)
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)
}
.frame(width: elementWidth, alignment: .leading)
}
.leftAligned()
.frame(width: geo.size.width / 3)

VStack(alignment: .leading, spacing: 4) {
Text(DataLocalizer.localize(path: "APP.GENERAL.MARGIN"))
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)

HStack {
VStack(alignment: .leading, spacing: 4) {
Text(self.marginValue)
.themeFont(fontSize: .small)
.themeColor(foreground: .textPrimary)
.minimumScaleFactor(0.5)

Text(self.marginMode)
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)
.minimumScaleFactor(0.5)
}

Spacer()

if self.isMarginAdjustable {
Group {
Text(DataLocalizer.localize(path: "APP.GENERAL.MARGIN"))
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)

HStack(spacing: 8) {
VStack(alignment: .leading, spacing: 4) {
Text(self.marginValue)
.themeFont(fontSize: .small)
.themeColor(foreground: .textPrimary)

Text(self.marginMode)
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)
}

let buttonContent = PlatformIconViewModel(type: .asset(name: "icon_edit", bundle: Bundle.dydxView),
size: CGSize(width: 20, height: 20),
templateColor: .textSecondary)
PlatformButtonViewModel(content: buttonContent,
type: PlatformButtonType.iconType) { [weak self] in
self?.handler?.onMarginEditAction?()
if self.isMarginAdjustable {

let buttonContent = PlatformIconViewModel(type: .asset(name: "icon_edit", bundle: Bundle.dydxView),
size: CGSize(width: 20, height: 20),
templateColor: .textSecondary)
PlatformButtonViewModel(content: buttonContent,
type: PlatformButtonType.iconType) { [weak self] in
self?.handler?.onMarginEditAction?()
}
.createView(parentStyle: parentStyle)
.frame(width: 32, height: 32)
.themeColor(background: .layer6)
.border(borderWidth: 1, cornerRadius: 7, borderColor: ThemeColor.SemanticColor.layer7.color)
}
.createView(parentStyle: parentStyle)
.frame(width: 32, height: 32)
.themeColor(background: .layer6)
.border(borderWidth: 1, cornerRadius: 7, borderColor: ThemeColor.SemanticColor.layer7.color)
}
}
.frame(width: elementWidth, alignment: .leading)
}
.leftAligned()
.frame(width: geo.size.width / 3)
}

}
.padding(.horizontal, 16)
}

private func createLogo( parentStyle: ThemeStyle) -> some View {
Expand All @@ -206,28 +210,33 @@ public class dydxPortfolioPositionItemViewModel: PlatformViewModel {
}
}

private func createMain(parentStyle: ThemeStyle) -> some View {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 2) {
Text(size ?? "")
.themeFont(fontType: .number, fontSize: .small)

token?.createView(parentStyle: parentStyle.themeFont(fontSize: .smallest))
private func createTopRowStats(parentStyle: ThemeStyle) -> some View {
HStack(alignment: .top, spacing: 0) {
VStack(alignment: .leading, spacing: 2) {
HStack(spacing: 4) {
Text(size ?? "")
.themeFont(fontType: .base, fontSize: .small)
.themeColor(foreground: .textPrimary)
token?.createView(parentStyle: parentStyle.themeFont(fontSize: .smallest))
}
Text(notionalValue ?? "")
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)
}

HStack(spacing: 2) {
Spacer(minLength: 8)
HStack(alignment: .top, spacing: 2) {
sideText
.createView(parentStyle: parentStyle.themeFont(fontSize: .smaller))
Text("@")
.themeFont(fontSize: .smaller)
.themeColor(foreground: .textTertiary)

Text(leverage ?? "")
.themeFont(fontType: .number, fontSize: .smaller)
.themeFont(fontType: .base, fontSize: .smaller)
.themeColor(foreground: .textPrimary)
}
}
.leftAligned()
.minimumScaleFactor(0.5)
}
}

Expand Down Expand Up @@ -282,7 +291,6 @@ public class dydxPortfolioPositionsViewModel: PlatformViewModel {
.borderAndClip(style: .circle, borderColor: .borderDefault)
Spacer()
}
.padding(.horizontal, 16)
.frame(width: UIScreen.main.bounds.width - 32)
.themeFont(fontSize: .small)
.themeColor(foreground: .textTertiary)
Expand Down
Loading