Skip to content

Commit ff88b00

Browse files
authored
ENG-536 Update UI for the Sep reward program (#446)
* FF and update profile text * Receipt * Skip block reward
1 parent 5549c72 commit ff88b00

File tree

10 files changed

+170
-30
lines changed

10 files changed

+170
-30
lines changed

dydx/dydxFormatter/dydxFormatter/_Utils/dydxFeatureFlag.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public enum dydxBoolFeatureFlag: String, CaseIterable {
1616
case simple_ui = "ff_simple_ui"
1717
case privy_ios = "ff_privy_ios"
1818
case turnkey_ios = "ff_turnkey_ios"
19+
case rewards_sep_2025 = "ff_rewards_sep_2025"
1920

2021
var defaultValue: Bool {
2122
switch self {
@@ -31,6 +32,8 @@ public enum dydxBoolFeatureFlag: String, CaseIterable {
3132
return false
3233
case .turnkey_ios:
3334
return false
35+
case .rewards_sep_2025:
36+
return false
3437
}
3538
}
3639

dydx/dydxPresenters/dydxPresenters/_Features/features.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,26 @@
112112
}
113113
]
114114
}
115+
},
116+
{
117+
"title":{
118+
"text":"Rewards Sep 2025"
119+
},
120+
"field":{
121+
"field":"ff_rewards_sep_2025",
122+
"optional":true,
123+
"type" : "bool",
124+
"options" : [
125+
{
126+
"text": "yes",
127+
"value" : 1
128+
},
129+
{
130+
"text": "no",
131+
"value" : 0
132+
}
133+
]
134+
}
115135
}
116136
]
117137
}

dydx/dydxPresenters/dydxPresenters/_v4/GlobalWorkers/Workers/dydxAlertsWorker.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Foundation
1212
import ParticlesKit
1313
import RoutingKit
1414
import Utilities
15+
import dydxFormatter
1516

1617
extension Abacus.NotificationType {
1718
var infoType: EInfoType {
@@ -53,6 +54,12 @@ final class dydxAlertsWorker: BaseWorker {
5354
// display alerts in chronological order they were received
5455
.sorted { $0.updateTimeInMilliseconds < $1.updateTimeInMilliseconds }
5556
.forEach { alert in
57+
// Skip block reward notification for Sep 2025
58+
if dydxBoolFeatureFlag.rewards_sep_2025.isEnabled,
59+
alert.id.starts(with: "blockReward:") {
60+
return
61+
}
62+
5663
let link = alert.link
5764
let actions = (link != nil) ? [ErrorAction(text: DataLocalizer.localize(path: "APP.GENERAL.VIEW")) {
5865
Router.shared?.navigate(to: RoutingRequest(path: link!), animated: true, completion: nil)

dydx/dydxPresenters/dydxPresenters/_v4/Profile/TradingRewards/Components/dydxRewardsLaunchIncentivesPresenter.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class dydxRewardsLaunchIncentivesPresenter: HostedViewPresenter<dydxRewar
2525
super.init()
2626

2727
viewModel = dydxRewardsLaunchIncentivesViewModel()
28+
viewModel?.isSep2025 = dydxBoolFeatureFlag.rewards_sep_2025.isEnabled
2829
}
2930

3031
public override func start() {

dydx/dydxPresenters/dydxPresenters/_v4/Receipt/dydxReceiptPresenter.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class dydxReceiptPresenter: HostedViewPresenter<dydxReceiptViewModel>, dydxRecei
3333
let exchangeRateViewModel = dydxReceiptItemViewModel()
3434
let exchangeReceivedViewModel = dydxReceiptItemViewModel()
3535
let slippageViewModel = dydxReceiptItemViewModel()
36-
let equlityViewModel = dydxReceiptEquityViewModel()
36+
let equityViewModel = dydxReceiptEquityViewModel()
3737
let rewardsViewModel = dydxReceiptRewardsViewModel()
3838
let transferDurationViewModel = dydxReceiptItemViewModel()
3939

@@ -42,7 +42,7 @@ class dydxReceiptPresenter: HostedViewPresenter<dydxReceiptViewModel>, dydxRecei
4242

4343
viewModel = dydxReceiptViewModel()
4444

45-
equlityViewModel.usdcTokenName = dydxTokenConstants.usdcTokenName
45+
equityViewModel.usdcTokenName = dydxTokenConstants.usdcTokenName
4646
rewardsViewModel.nativeTokenLogoUrl = dydxTokenConstants.nativeTokenLogoUrl
4747
}
4848

@@ -75,7 +75,7 @@ class dydxReceiptPresenter: HostedViewPresenter<dydxReceiptViewModel>, dydxRecei
7575
case .positionleverage:
7676
return positionLeverageViewModel
7777
case .equity:
78-
return equlityViewModel
78+
return equityViewModel
7979
case .exchangerate:
8080
return exchangeRateViewModel
8181
case .exchangereceived:

dydx/dydxPresenters/dydxPresenters/_v4/Receipt/dydxTradeReceiptPresenter.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ final class dydxTradeReceiptPresenter: dydxReceiptPresenter {
5858
self?.updatePositionMargin(position: position)
5959
self?.updatePositionLeverage(position: position)
6060
self?.updateTradingFee(tradeSummary: tradeSummary)
61-
self?.updateTradingRewards(tradeSummary: tradeSummary)
61+
if dydxBoolFeatureFlag.rewards_sep_2025.isEnabled {
62+
self?.updateTradingRewardsSep2025(tradeSummary: tradeSummary)
63+
} else {
64+
self?.updateTradingRewards(tradeSummary: tradeSummary)
65+
}
6266
}
6367
.store(in: &subscriptions)
6468

@@ -138,6 +142,7 @@ final class dydxTradeReceiptPresenter: dydxReceiptPresenter {
138142
}
139143

140144
private func updateTradingRewards(tradeSummary: TradeInputSummary?) {
145+
rewardsViewModel.isSep2025 = false
141146
guard let rewards = tradeSummary?.reward?.doubleValue,
142147
let value = dydxFormatter.shared.localFormatted(number: abs(rewards), digits: 6) else {
143148
rewardsViewModel.rewards = nil
@@ -153,6 +158,18 @@ final class dydxTradeReceiptPresenter: dydxReceiptPresenter {
153158
}
154159
}
155160

161+
private func updateTradingRewardsSep2025(tradeSummary: TradeInputSummary?) {
162+
rewardsViewModel.isSep2025 = true
163+
guard let fee = tradeSummary?.fee?.doubleValue else {
164+
rewardsViewModel.rewardsSep2025 = nil
165+
return
166+
}
167+
168+
var rewards = max(0.0, fee) * 0.5
169+
let amount = dydxFormatter.shared.dollar(number: rewards, digits: 2)
170+
rewardsViewModel.rewardsSep2025 = SignedAmountViewModel(text: amount, sign: .none, coloringOption: .signOnly)
171+
}
172+
156173
private func updateEquityChange(account: Subaccount) {
157174
let before: AmountTextModel?
158175
if let beforeAmount = account.equity?.current?.doubleValue {
@@ -167,6 +184,6 @@ final class dydxTradeReceiptPresenter: dydxReceiptPresenter {
167184
after = nil
168185
}
169186

170-
equlityViewModel.equityChange = .init(before: before, after: after)
187+
equityViewModel.equityChange = .init(before: before, after: after)
171188
}
172189
}

dydx/dydxPresenters/dydxPresenters/_v4/Receipt/dydxTransferReceiptViewPresenter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,6 @@ final class dydxTransferReceiptViewPresenter: dydxReceiptPresenter {
211211
after = nil
212212
}
213213

214-
equlityViewModel.equityChange = .init(before: before, after: after)
214+
equityViewModel.equityChange = .init(before: before, after: after)
215215
}
216216
}

dydx/dydxPresenters/dydxPresenters/_v4/SimpleUI/MarketInfo/components/dydxSimpleUiMarketLaunchableViewPresenter.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import dydxStateManager
1616
import Abacus
1717
import dydxFormatter
1818
import SwiftUI
19-
import Statsig
2019

2120
protocol dydxSimpleUiMarketLaunchableViewPresenterProtocol: HostedViewPresenterProtocol {
2221
var viewModel: dydxSimpleUiMarketLaunchableViewModel? { get }

dydx/dydxViews/dydxViews/_v4/Profile/TradingRewards/Components/dydxRewardsLaunchIncentivesView.swift

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,31 @@ public class dydxRewardsLaunchIncentivesViewModel: PlatformViewModel {
1515
@Published public var points: String?
1616
@Published public var aboutAction: (() -> Void)?
1717
@Published public var leaderboardAction: (() -> Void)?
18+
@Published public var isSep2025: Bool = false
1819

1920
public static var previewValue: dydxRewardsLaunchIncentivesViewModel = {
2021
let vm = dydxRewardsLaunchIncentivesViewModel()
2122
vm.seasonOrdinal = "1"
2223
return vm
2324
}()
2425

25-
private let launchIncentivesFormatted: AttributedString = {
26-
guard let launchIncentives = DataLocalizer.shared?.localize(path: "APP.REWARDS_SURGE_APRIL_2025.SURGE_HEADLINE", params: nil) else { return .init() }
26+
private lazy var launchIncentivesFormatted: AttributedString = {
27+
let launchIncentives: String?
28+
if isSep2025 {
29+
launchIncentives = DataLocalizer.localize(path: "APP.REWARDS_SURGE_APRIL_2025.SURGE") + ": " +
30+
DataLocalizer.localize(path: "APP.REWARDS_SURGE_APRIL_2025.SURGE_HEADLINE_SEP_2025",
31+
params: [
32+
"REWARD_AMOUNT": "$1M",
33+
"REBATE_PERCENT": "50%"
34+
])
35+
} else {
36+
launchIncentives = DataLocalizer.localize(path: "APP.REWARDS_SURGE_APRIL_2025.SURGE_HEADLINE", params: nil)
37+
}
38+
guard let launchIncentives else { return .init() }
39+
2740
return AttributedString(launchIncentives)
28-
.themeFont(fontType: .base, fontSize: .medium)
29-
.themeColor(foreground: .textPrimary)
41+
.themeFont(fontType: .base, fontSize: .medium)
42+
.themeColor(foreground: .textPrimary)
3043
}()
3144

3245
private var pointsFormatted: AttributedString {
@@ -104,10 +117,22 @@ public class dydxRewardsLaunchIncentivesViewModel: PlatformViewModel {
104117
public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView {
105118
PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in
106119
guard let self = self else { return AnyView(PlatformView.nilView) }
120+
121+
let body: String
122+
if self.isSep2025 {
123+
body = DataLocalizer.localize(path: "APP.REWARDS_SURGE_APRIL_2025.SURGE_BODY_SEP_2025",
124+
params: [
125+
"REWARD_AMOUNT": "$1M",
126+
"REBATE_PERCENT": "50%"
127+
])
128+
} else {
129+
body = DataLocalizer.localize(path: "APP.REWARDS_SURGE_APRIL_2025.SURGE_BODY", params: nil)
130+
}
131+
107132
return VStack(spacing: 16) {
108133
self.createEstimateSubCard()
109134
VStack(alignment: .leading, spacing: 8) {
110-
HStack {
135+
HStack(alignment: .center) {
111136
Text(self.launchIncentivesFormatted)
112137

113138
Text(DataLocalizer.localize(path: "APP.GENERAL.ACTIVE"))
@@ -119,9 +144,11 @@ public class dydxRewardsLaunchIncentivesViewModel: PlatformViewModel {
119144

120145
Spacer()
121146
}
122-
Text(DataLocalizer.shared?.localize(path: "APP.REWARDS_SURGE_APRIL_2025.SURGE_BODY", params: nil) ?? "")
147+
148+
Text(body)
123149
.themeFont(fontType: .base, fontSize: .small)
124150
.themeColor(foreground: .textTertiary)
151+
125152
HStack(spacing: 8) {
126153
Text(DataLocalizer.shared?.localize(path: "APP.TRADING_REWARDS.POWERED_BY", params: nil) ?? "")
127154
.themeFont(fontType: .base, fontSize: .smaller)

dydx/dydxViews/dydxViews/_v4/Receipt/Components/dydxReceiptRewardsView.swift

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ public class dydxReceiptRewardsViewModel: PlatformViewModel {
1414
@Published public var rewards: SignedAmountViewModel?
1515
@Published public var nativeTokenLogoUrl: URL?
1616

17+
@Published public var isSep2025: Bool = false
18+
@Published public var rewardsSep2025: SignedAmountViewModel?
19+
20+
@Published private var presented: Bool = false
21+
private lazy var presentedBindng = Binding(
22+
get: { [weak self] in self?.presented == true },
23+
set: { [weak self] in self?.presented = $0 }
24+
)
25+
1726
public init() { }
1827

1928
public static var previewValue: dydxReceiptRewardsViewModel {
@@ -26,29 +35,86 @@ public class dydxReceiptRewardsViewModel: PlatformViewModel {
2635
PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in
2736
guard let self = self else { return AnyView(PlatformView.nilView) }
2837

29-
return AnyView(
30-
HStack(spacing: 4) {
31-
Text(DataLocalizer.localize(path: "APP.GENERAL.EST_REWARDS"))
38+
if self.isSep2025 {
39+
let attributedTitle = AttributedString(DataLocalizer.localize(path: "APP.GENERAL.EST_REWARDS"))
40+
.themeFont(fontSize: .small)
41+
.themeColor(foreground: .textTertiary)
42+
let label = Text(attributedTitle.dottedUnderline(foreground: .textTertiary))
43+
.themeColor(foreground: .textTertiary)
44+
.wrappedViewModel
45+
let content = VStack(alignment: .leading, spacing: 8) {
46+
Text(DataLocalizer.localize(path: "TRADE.MAXIMUM_REWARDS.BODY",
47+
params: ["REWARD_AMOUNT": "1M"]))
3248
.themeFont(fontSize: .small)
3349
.themeColor(foreground: .textTertiary)
34-
.lineLimit(1)
35-
if let nativeTokenLogoUrl = self.nativeTokenLogoUrl {
36-
PlatformIconViewModel(type: .url(url: nativeTokenLogoUrl),
37-
size: CGSize(width: 18, height: 18))
38-
.createView(parentStyle: style)
50+
}
51+
let labelView = label.createView(parentStyle: style)
52+
.onTapGesture { [weak self] in
53+
self?.presented.toggle()
54+
}
55+
.popover(present: presentedBindng, attributes: {
56+
$0.position = .absolute(
57+
originAnchor: .top,
58+
popoverAnchor: .bottom
59+
)
60+
$0.sourceFrameInset = .init(top: 0, left: 0, bottom: -16, right: 0)
61+
$0.presentation.animation = .none
62+
$0.blocksBackgroundTouches = true
63+
$0.onTapOutside = { [weak self] in
64+
self?.presented = false
65+
}
66+
}, view: {
67+
content
68+
.padding(.vertical, 10)
69+
.padding(.horizontal, 12)
70+
.themeColor(background: .layer5)
71+
.borderAndClip(style: .cornerRadius(8), borderColor: .layer6, lineWidth: 1)
72+
.environmentObject(ThemeSettings.shared)
73+
})
74+
75+
return AnyView(
76+
HStack(spacing: 4) {
77+
labelView
78+
79+
Spacer()
80+
81+
if let rewards = self.rewardsSep2025 {
82+
rewards.createView(parentStyle: style
83+
.themeFont(fontType: .number, fontSize: .small)
84+
.themeColor(foreground: .textPrimary))
85+
.lineLimit(1)
86+
} else {
87+
dydxReceiptEmptyView.emptyValue
88+
}
89+
3990
}
91+
)
92+
} else {
93+
return AnyView(
94+
HStack(spacing: 4) {
95+
Text(DataLocalizer.localize(path: "APP.GENERAL.EST_REWARDS"))
96+
.themeFont(fontSize: .small)
97+
.themeColor(foreground: .textTertiary)
98+
.lineLimit(1)
99+
if let nativeTokenLogoUrl = self.nativeTokenLogoUrl {
100+
PlatformIconViewModel(type: .url(url: nativeTokenLogoUrl),
101+
size: CGSize(width: 18, height: 18))
102+
.createView(parentStyle: style)
103+
}
104+
105+
Spacer()
40106

41-
Spacer()
42-
if let rewards = self.rewards {
43-
rewards.createView(parentStyle: style
44-
.themeFont(fontType: .number, fontSize: .small)
45-
.themeColor(foreground: .textPrimary))
107+
if let rewards = self.rewards {
108+
rewards.createView(parentStyle: style
109+
.themeFont(fontType: .number, fontSize: .small)
110+
.themeColor(foreground: .textPrimary))
46111
.lineLimit(1)
47-
} else {
48-
dydxReceiptEmptyView.emptyValue
112+
} else {
113+
dydxReceiptEmptyView.emptyValue
114+
}
49115
}
50-
}
51-
)
116+
)
117+
}
52118
}
53119
}
54120
}

0 commit comments

Comments
 (0)