Skip to content

Commit c2817ea

Browse files
committed
feat: fixed denomination UI
1 parent 72ae407 commit c2817ea

49 files changed

Lines changed: 318 additions & 213 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,20 @@ extension ExplorePointOfUse {
5959
let deeplink: String?
6060
let savingsBasisPoints: Int // in basis points 1 = 0.001%
6161
let denominationsType: String?
62+
let denominations: [Int]
6263
let redeemType: String?
6364

65+
init(merchantId: String, paymentMethod: PaymentMethod, type: `Type`, deeplink: String?, savingsBasisPoints: Int, denominationsType: String?, denominations: [Int] = [], redeemType: String?) {
66+
self.merchantId = merchantId
67+
self.paymentMethod = paymentMethod
68+
self.type = type
69+
self.deeplink = deeplink
70+
self.savingsBasisPoints = savingsBasisPoints
71+
self.denominationsType = denominationsType
72+
self.denominations = denominations
73+
self.redeemType = redeemType
74+
}
75+
6476
func toSavingPercentages() -> Double {
6577
return Double(savingsBasisPoints) / 100
6678
}
@@ -234,7 +246,7 @@ extension ExplorePointOfUse: RowDecodable {
234246
let denominationsType = row[ExplorePointOfUse.denominationsType]
235247
let redeemType = row[ExplorePointOfUse.redeemType]
236248
category = .merchant(Merchant(merchantId: merchantId, paymentMethod: Merchant.PaymentMethod(rawValue: paymentMethodRaw)!,
237-
type: type, deeplink: deeplink, savingsBasisPoints: savingsPercentage, denominationsType: denominationsType, redeemType: redeemType))
249+
type: type, deeplink: deeplink, savingsBasisPoints: savingsPercentage, denominationsType: denominationsType, denominations: [], redeemType: redeemType))
238250
} else if let manufacturer = try? row.get(ExplorePointOfUse.manufacturer) {
239251
let type: Atm.`Type`! = .init(rawValue: row[ExplorePointOfUse.type])
240252
category = .atm(Atm(manufacturer: manufacturer, type: type))

DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import SwiftUI
2222
// MARK: - PointOfUseDetailsViewController
2323

2424
class PointOfUseDetailsViewController: UIViewController {
25-
internal let pointOfUse: ExplorePointOfUse
25+
internal var pointOfUse: ExplorePointOfUse
2626
internal let isShowAllHidden: Bool
2727

2828
@objc public var payWithDashHandler: (()->())?
@@ -55,7 +55,7 @@ class PointOfUseDetailsViewController: UIViewController {
5555
super.viewDidLoad()
5656
title = pointOfUse.name
5757
configureHierarchy()
58-
tryRefreshCtxToken()
58+
refreshTokenAndMerchantInfo()
5959
}
6060
}
6161

@@ -235,15 +235,27 @@ extension PointOfUseDetailsViewController {
235235
self.navigationController?.pushViewController(hostingController, animated: true)
236236
}
237237

238-
private func tryRefreshCtxToken() {
238+
private func refreshTokenAndMerchantInfo() {
239239
Task {
240-
do {
241-
try await CTXSpendService.shared.refreshToken()
242-
} catch CTXSpendError.tokenRefreshFailed {
243-
await showModalDialog(style: .warning, icon: .system("exclamationmark.triangle.fill"), heading: NSLocalizedString("Your session expired", comment: "DashSpend"), textBlock1: NSLocalizedString("It looks like you haven’t used DashSpend in a while. For security reasons, you’ve been logged out.\n\nPlease sign in again to continue exploring where to spend your Dash.", comment: "DashSpend"), positiveButtonText: NSLocalizedString("Dismiss", comment: ""))
240+
if try await tryRefreshCtxToken(), let merchantId = pointOfUse.merchant?.merchantId {
241+
let merchantInfo = try await CTXSpendService.shared.getMerchant(merchantId: merchantId)
242+
pointOfUse = pointOfUse.updatingMerchant(
243+
denominationsType: merchantInfo.denominationsType,
244+
denominations: merchantInfo.denominations.compactMap { Int($0) }
245+
)
244246
}
245247
}
246248
}
249+
250+
private func tryRefreshCtxToken() async throws -> Bool {
251+
do {
252+
try await CTXSpendService.shared.refreshToken()
253+
return true
254+
} catch CTXSpendError.tokenRefreshFailed {
255+
await showModalDialog(style: .warning, icon: .system("exclamationmark.triangle.fill"), heading: NSLocalizedString("Your session expired", comment: "DashSpend"), textBlock1: NSLocalizedString("It looks like you haven’t used DashSpend in a while. For security reasons, you’ve been logged out.\n\nPlease sign in again to continue exploring where to spend your Dash.", comment: "DashSpend"), positiveButtonText: NSLocalizedString("Dismiss", comment: ""))
256+
return false
257+
}
258+
}
247259
}
248260

249261

@@ -290,3 +302,40 @@ extension UIHostingController: NavigationBarDisplayable {
290302
var isBackButtonHidden: Bool { true }
291303
var isNavigationBarHidden: Bool { true }
292304
}
305+
306+
extension ExplorePointOfUse {
307+
func updatingMerchant(denominationsType: String?, denominations: [Int]) -> ExplorePointOfUse {
308+
guard case .merchant(let currentMerchant) = category else { return self }
309+
310+
let updatedMerchant = ExplorePointOfUse.Merchant(
311+
merchantId: currentMerchant.merchantId,
312+
paymentMethod: currentMerchant.paymentMethod,
313+
type: currentMerchant.type,
314+
deeplink: currentMerchant.deeplink,
315+
savingsBasisPoints: currentMerchant.savingsBasisPoints,
316+
denominationsType: denominationsType,
317+
denominations: denominations,
318+
redeemType: currentMerchant.redeemType
319+
)
320+
321+
return ExplorePointOfUse(
322+
id: id,
323+
name: name,
324+
category: .merchant(updatedMerchant),
325+
active: active,
326+
city: city,
327+
territory: territory,
328+
address1: address1,
329+
address2: address2,
330+
address3: address3,
331+
address4: address4,
332+
latitude: latitude,
333+
longitude: longitude,
334+
website: website,
335+
phone: phone,
336+
logoLocation: logoLocation,
337+
coverImage: coverImage,
338+
source: source
339+
)
340+
}
341+
}

DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/MerchantFiltersView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ struct MerchantFiltersView: View {
109109
}
110110

111111
RadioButtonRow(
112-
title: NSLocalizedString("Fixed amounts", comment: "Explore Dash: Filters"),
112+
title: NSLocalizedString("Fixed denominated amounts", comment: "Explore Dash: Filters"),
113113
isSelected: viewModel.denominationFixed,
114114
style: .checkbox
115115
) {

DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayScreen.swift

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -122,27 +122,52 @@ struct DashSpendPayScreen: View {
122122

123123
Spacer()
124124

125-
NumericKeyboardView(
126-
value: $viewModel.input,
127-
showDecimalSeparator: true,
128-
actionButtonText: NSLocalizedString("Preview", comment: ""),
129-
actionEnabled: viewModel.error == nil && !viewModel.showLimits && !viewModel.isLoading && viewModel.hasValidLimits,
130-
inProgress: viewModel.isProcessingPayment,
131-
actionHandler: {
132-
if !viewModel.isUserSignedIn {
133-
showSignInError()
134-
return
125+
if viewModel.isFixedDenomination {
126+
MerchantDenominations(
127+
denominations: viewModel.denominations,
128+
selectedDenomination: viewModel.selectedDenomination,
129+
actionEnabled: viewModel.selectedDenomination != nil && viewModel.error == nil && !viewModel.isLoading,
130+
onDenominationSelected: { denom in
131+
viewModel.selectedDenomination = denom
132+
},
133+
actionHandler: {
134+
if !viewModel.isUserSignedIn {
135+
showSignInError()
136+
return
137+
}
138+
139+
showConfirmationDialog = true
135140
}
136-
137-
showConfirmationDialog = true
138-
}
139-
)
140-
.frame(maxWidth: .infinity)
141-
.frame(height: 320)
142-
.padding(.horizontal, 20)
143-
.padding(.bottom, 30)
144-
.background(Color.secondaryBackground)
145-
.cornerRadius(20)
141+
)
142+
.frame(maxWidth: .infinity)
143+
.padding(.top, 20)
144+
.padding(.horizontal, 20)
145+
.padding(.bottom, 30)
146+
.background(Color.secondaryBackground)
147+
.cornerRadius(20)
148+
} else {
149+
NumericKeyboardView(
150+
value: $viewModel.input,
151+
showDecimalSeparator: true,
152+
actionButtonText: NSLocalizedString("Preview", comment: ""),
153+
actionEnabled: viewModel.error == nil && !viewModel.showLimits && !viewModel.isLoading && viewModel.hasValidLimits,
154+
inProgress: viewModel.isProcessingPayment,
155+
actionHandler: {
156+
if !viewModel.isUserSignedIn {
157+
showSignInError()
158+
return
159+
}
160+
161+
showConfirmationDialog = true
162+
}
163+
)
164+
.frame(maxWidth: .infinity)
165+
.frame(height: 320)
166+
.padding(.horizontal, 20)
167+
.padding(.bottom, 30)
168+
.background(Color.secondaryBackground)
169+
.cornerRadius(20)
170+
}
146171
}
147172

148173
if justAuthenticated {

DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,17 @@ class DashSpendPayViewModel: NSObject, ObservableObject {
4949
@Published var minimumAmount: Decimal = 0
5050
@Published var maximumAmount: Decimal = 0
5151
@Published var error: Error? = nil
52+
@Published var isFixedDenomination: Bool = false
53+
@Published var denominations: [Int] = []
54+
@Published var selectedDenomination: Int? = nil {
55+
didSet {
56+
if let denom = selectedDenomination {
57+
input = String(denom)
58+
} else {
59+
input = "0"
60+
}
61+
}
62+
}
5263
@Published var input: String = "0" {
5364
didSet {
5465
// Replace the initial "0" when entering a new digit
@@ -100,6 +111,11 @@ class DashSpendPayViewModel: NSObject, ObservableObject {
100111
self.merchantId = merchantId
101112
}
102113

114+
if let denomType = merchant.merchant?.denominationsType {
115+
self.isFixedDenomination = denomType == DenominationType.Fixed.rawValue
116+
self.denominations = merchant.merchant?.denominations.compactMap { Int($0) } ?? []
117+
}
118+
103119
super.init()
104120

105121
// Initialize with current sign-in state
@@ -153,7 +169,6 @@ class DashSpendPayViewModel: NSObject, ObservableObject {
153169
customIconProvider.updateIcon(txId: transaction.txHashData, iconUrl: merchantIconUrl)
154170
saveGiftCardDummy(txHashData: transaction.txHashData, giftCardId: response.paymentId)
155171

156-
157172
return transaction.txHashData
158173
}
159174

@@ -166,8 +181,8 @@ class DashSpendPayViewModel: NSObject, ObservableObject {
166181
body += "min: \(minimumAmount)\n"
167182
body += "max: \(maximumAmount)\n"
168183
body += "discount: \(savingsFraction)\n"
169-
// body += "denominations type: \(denominationsType)\n" TODO: fixed denoms
170-
// body += "denominations: \(denominations)\n"
184+
body += "fixed denomination: \(isFixedDenomination)\n"
185+
body += "denominations: \(denominations)\n"
171186
body += "\n"
172187

173188
body += "Purchase Details\n"
@@ -228,8 +243,12 @@ class DashSpendPayViewModel: NSObject, ObservableObject {
228243
savingsFraction = Decimal(merchantInfo.savingsPercentage) / Decimal(10000)
229244

230245
if merchantInfo.denominationType == .Range {
246+
isFixedDenomination = false
231247
minimumAmount = Decimal(merchantInfo.minimumCardPurchase)
232248
maximumAmount = Decimal(merchantInfo.maximumCardPurchase)
249+
} else {
250+
isFixedDenomination = true
251+
denominations = merchantInfo.denominations.compactMap { Int($0) }
233252
}
234253

235254
checkAmountForErrors()

DashWallet/Sources/UI/SwiftUI Components/MerchantDenominations.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import SwiftUI
2020
struct MerchantDenominations: View {
2121
let denominations: [Int]
2222
let currency: String = "USD"
23-
@Binding var selectedDenomination: Int?
24-
let canContinue: Bool = true
25-
let onDenominationSelected: (Int) -> Void = { _ in }
26-
let onContinue: () -> Void = { }
23+
let selectedDenomination: Int?
24+
let actionEnabled: Bool
25+
let onDenominationSelected: (Int) -> Void
26+
let actionHandler: () -> Void
2727

2828
private var numberFormatter: NumberFormatter {
2929
let formatter = NumberFormatter()
@@ -40,11 +40,11 @@ struct MerchantDenominations: View {
4040

4141
var body: some View {
4242
VStack(alignment: .leading, spacing: 0) {
43-
Text(NSLocalizedString("Select amount", comment: "DashSpend denomination selection"))
43+
Text(NSLocalizedString("Select amount", comment: "DashSpend"))
4444
.font(.h5Bold)
4545
.foregroundColor(.primaryText)
4646

47-
Text(NSLocalizedString("Select fixed amount", comment: "DashSpend denomination selection subtitle"))
47+
Text(NSLocalizedString("This merchant sells gift cards at fixed prices", comment: "DashSpend"))
4848
.font(.body2)
4949
.foregroundColor(.secondaryText)
5050
.padding(.top, 4)
@@ -68,8 +68,8 @@ struct MerchantDenominations: View {
6868
style: .filled,
6969
size: .large,
7070
stretch: true,
71-
isEnabled: selectedDenomination != nil && selectedDenomination != 0 && canContinue,
72-
action: onContinue
71+
isEnabled: selectedDenomination != nil && selectedDenomination != 0 && actionEnabled,
72+
action: actionHandler
7373
)
7474
.padding(.top, 20)
7575
}
@@ -97,7 +97,7 @@ private struct DenominationChip: View {
9797
.overlay(
9898
RoundedRectangle(cornerRadius: 14)
9999
.stroke(
100-
isSelected ? Color.dashBlue : Color.gray400,
100+
isSelected ? Color.dashBlue : Color.primaryText.opacity(0.08),
101101
lineWidth: 1.5
102102
)
103103
)

DashWallet/ar.lproj/Localizable.strings

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@
971971
"First minimum deposit" = "First minimum deposit";
972972

973973
/* Explore Dash: Filters */
974-
"Fixed amounts" = "Fixed amounts";
974+
"Fixed denominated amounts" = "Fixed denominated amounts";
975975

976976
/* Explore Dash: Filters */
977977
"Flexible amounts" = "Flexible amounts";
@@ -2208,9 +2208,6 @@
22082208
Block explorer selection title */
22092209
"Select block explorer" = "Select block explorer";
22102210

2211-
/* DashSpend denomination selection subtitle */
2212-
"Select fixed amount" = "Select fixed amount";
2213-
22142211
/* No comment provided by engineer. */
22152212
"Select from Gallery" = "Select from Gallery";
22162213

@@ -2539,6 +2536,9 @@
25392536
/* DashSpend */
25402537
"This merchant is currently unavailable. Please try again later or choose a different merchant." = "This merchant is currently unavailable. Please try again later or choose a different merchant.";
25412538

2539+
/* DashSpend */
2540+
"This merchant sells gift cards at fixed prices" = "This merchant sells gift cards at fixed prices";
2541+
25422542
/* No comment provided by engineer. */
25432543
"This PIN will be required to unlock your app every time when you use it." = "سيكون الرقم السري هذا مطلوبًا لإلغاء قفل تطبيقك في كل مرة تستخدمه.";
25442544

DashWallet/bg.lproj/Localizable.strings

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@
971971
"First minimum deposit" = "First minimum deposit";
972972

973973
/* Explore Dash: Filters */
974-
"Fixed amounts" = "Fixed amounts";
974+
"Fixed denominated amounts" = "Fixed denominated amounts";
975975

976976
/* Explore Dash: Filters */
977977
"Flexible amounts" = "Flexible amounts";
@@ -2208,9 +2208,6 @@
22082208
Block explorer selection title */
22092209
"Select block explorer" = "Select block explorer";
22102210

2211-
/* DashSpend denomination selection subtitle */
2212-
"Select fixed amount" = "Select fixed amount";
2213-
22142211
/* No comment provided by engineer. */
22152212
"Select from Gallery" = "Select from Gallery";
22162213

@@ -2539,6 +2536,9 @@
25392536
/* DashSpend */
25402537
"This merchant is currently unavailable. Please try again later or choose a different merchant." = "This merchant is currently unavailable. Please try again later or choose a different merchant.";
25412538

2539+
/* DashSpend */
2540+
"This merchant sells gift cards at fixed prices" = "This merchant sells gift cards at fixed prices";
2541+
25422542
/* No comment provided by engineer. */
25432543
"This PIN will be required to unlock your app every time when you use it." = "Този ПИН ще бъде необходим за отключване на приложението ви всеки път, когато го използвате.";
25442544

0 commit comments

Comments
 (0)