Skip to content

Commit d8a5daa

Browse files
committed
manage balance codes screen
1 parent 7da4beb commit d8a5daa

11 files changed

Lines changed: 892 additions & 22 deletions

app/network/Main/Account/AccountNavStackView.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ struct AccountNavStackView: View {
184184
countries: providerCountries
185185
)
186186
.navigationTitle("Blocked Locations")
187+
188+
case .transferBalanceCodes:
189+
190+
TransferBalanceCodesView(api: urApiService)
191+
.navigationTitle("Balance Codes")
187192

188193
}
189194

app/network/Main/Account/AccountNavStackViewModel.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ enum AccountNavigationPath: Hashable {
1515
case wallet(_ wallet: SdkAccountWallet)
1616
case payout(payment: SdkAccountPayment, accountPoint: SdkAccountPoint?)
1717
case blockedLocations
18+
case transferBalanceCodes
1819
}
1920

2021
extension AccountNavStackView {

app/network/Main/Account/AccountRootView.swift

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -120,33 +120,35 @@ struct AccountRootView: View {
120120

121121
}
122122

123-
Spacer().frame(height: 8)
124-
125123
/**
126124
* only display usage bar to users with basic plans
127125
*/
128-
129-
UsageBar(
130-
availableByteCount: subscriptionBalanceViewModel.availableByteCount,
131-
pendingByteCount: subscriptionBalanceViewModel.pendingByteCount,
132-
usedByteCount: subscriptionBalanceViewModel.usedBalanceByteCount,
133-
meanReliabilityWeight: meanReliabilityWeight,
134-
totalReferrals: referralLinkViewModel.totalReferrals
135-
)
136-
137-
HStack {
126+
if (!isPro) {
138127

139-
Spacer()
128+
Spacer().frame(height: 8)
129+
130+
UsageBar(
131+
availableByteCount: subscriptionBalanceViewModel.availableByteCount,
132+
pendingByteCount: subscriptionBalanceViewModel.pendingByteCount,
133+
usedByteCount: subscriptionBalanceViewModel.usedBalanceByteCount,
134+
meanReliabilityWeight: meanReliabilityWeight,
135+
totalReferrals: referralLinkViewModel.totalReferrals
136+
)
140137

141-
Button(action: {
142-
viewModel.isPresentedRedeemBalanceCodeSheet = true
143-
}) {
144-
Text("Redeem Balance Code")
145-
.font(themeManager.currentTheme.secondaryBodyFont)
138+
HStack {
139+
140+
Spacer()
141+
142+
Button(action: {
143+
viewModel.isPresentedRedeemBalanceCodeSheet = true
144+
}) {
145+
Text("Redeem Balance Code")
146+
.font(themeManager.currentTheme.secondaryBodyFont)
147+
}
148+
146149
}
147150

148151
}
149-
150152

151153
Divider()
152154
.background(themeManager.currentTheme.borderBaseColor)

app/network/Main/Account/BlockedLocations/BlockedLocationsViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ extension BlockedLocationsView {
1414
@MainActor
1515
class ViewModel: ObservableObject {
1616

17-
var api: UrApiServiceProtocol
17+
let api: UrApiServiceProtocol
1818

1919
@Published var isInitializing: Bool = true
2020
@Published var isLoading: Bool = false

app/network/Main/Account/Settings/SettingsForm-iOS.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ struct SettingsForm_iOS: View {
142142
}
143143
}
144144

145-
Section("Authentication") {
145+
Section("Account") {
146146
/**
147147
* Update referral code
148148
*/
@@ -169,9 +169,20 @@ struct SettingsForm_iOS: View {
169169
}
170170
}
171171

172+
HStack {
173+
Text("Balance Codes")
174+
.font(themeManager.currentTheme.bodyFont)
175+
Spacer()
176+
Image(systemName: "chevron.right")
177+
.foregroundColor(themeManager.currentTheme.textMutedColor)
178+
}
179+
.contentShape(Rectangle())
180+
.onTapGesture {
181+
navigate(.transferBalanceCodes)
182+
}
183+
172184
}
173185

174-
175186
Section("Connections") {
176187

177188
/**
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//
2+
// TransferBalanceCodesView.swift
3+
// URnetwork
4+
//
5+
// Created by Stuart Kuentzel on 1/17/26.
6+
//
7+
8+
import SwiftUI
9+
10+
struct TransferBalanceCodesView: View {
11+
12+
@StateObject private var viewModel: ViewModel
13+
@EnvironmentObject var themeManager: ThemeManager
14+
@EnvironmentObject var subscriptionBalanceViewModel: SubscriptionBalanceViewModel
15+
let api: UrApiServiceProtocol
16+
17+
init(
18+
api: UrApiServiceProtocol,
19+
) {
20+
_viewModel = .init(wrappedValue: .init(api: api))
21+
self.api = api
22+
}
23+
24+
var body: some View {
25+
26+
Group {
27+
28+
if (viewModel.isInitializing) {
29+
VStack {
30+
31+
Spacer()
32+
33+
ProgressView()
34+
.progressViewStyle(CircularProgressViewStyle())
35+
36+
Spacer()
37+
}
38+
.frame(maxWidth: .infinity, maxHeight: .infinity)
39+
} else {
40+
41+
if viewModel.redeemedBalanceCodes.isEmpty {
42+
VStack {
43+
Text("No balance codes found")
44+
.font(themeManager.currentTheme.bodyFont)
45+
.foregroundStyle(themeManager.currentTheme.textMutedColor)
46+
}
47+
.frame(maxWidth: .infinity, maxHeight: .infinity)
48+
} else {
49+
50+
List {
51+
Section {
52+
ForEach(viewModel.redeemedBalanceCodes, id: \.balanceCodeId?.idStr) { balanceCode in
53+
let masked: String = {
54+
let keep = 3
55+
guard balanceCode.secret.count > keep * 2 else { return String(repeating: ".", count: max(balanceCode.secret.count, 0)) }
56+
let start = balanceCode.secret.prefix(keep)
57+
let end = balanceCode.secret.suffix(keep)
58+
let middleCount = balanceCode.secret.count - (keep * 2)
59+
return start + String(repeating: ".", count: middleCount) + end
60+
}()
61+
62+
HStack {
63+
Text(masked)
64+
Spacer()
65+
if let redeemTime = balanceCode.redeemTime {
66+
Text(redeemTime.format("Jan 2, 2006"))
67+
}
68+
}
69+
}
70+
} header: {
71+
HStack {
72+
Text("Code")
73+
.foregroundStyle(themeManager.currentTheme.textMutedColor)
74+
Spacer()
75+
Text("Redeemed")
76+
.foregroundStyle(themeManager.currentTheme.textMutedColor)
77+
}
78+
.font(themeManager.currentTheme.bodyFont.weight(.semibold))
79+
.textCase(nil) // prevent automatic uppercasing in some styles
80+
.padding(.vertical, 4)
81+
}
82+
}
83+
.listStyle(.inset)
84+
85+
}
86+
}
87+
}
88+
.toolbar {
89+
90+
ToolbarItem(placement: .primaryAction) {
91+
Button(action: {
92+
viewModel.displayRedeemSheet = true
93+
}) {
94+
Image(systemName: "plus")
95+
}
96+
}
97+
}
98+
.sheet(isPresented: $viewModel.displayRedeemSheet) {
99+
VStack {
100+
101+
RedeemBalanceCodeSheet(
102+
closeSheet: {
103+
viewModel.displayRedeemSheet = false
104+
},
105+
onSuccess: {
106+
107+
viewModel.displayRedeemSheet = false
108+
109+
// start polling
110+
subscriptionBalanceViewModel.startPolling()
111+
112+
Task {
113+
await viewModel.getRedeemedBalanceCodes()
114+
}
115+
116+
},
117+
api: self.api
118+
)
119+
120+
}
121+
.background(themeManager.currentTheme.backgroundColor)
122+
}
123+
}
124+
}
125+
126+
//#Preview {
127+
// TransferBalanceCodesView(
128+
// api: MockUrApiService()
129+
// )
130+
//}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// TransferBalanceCodesViewModel.swift
3+
// URnetwork
4+
//
5+
// Created by Stuart Kuentzel on 1/17/26.
6+
//
7+
8+
import Foundation
9+
import URnetworkSdk
10+
11+
extension TransferBalanceCodesView {
12+
13+
@MainActor
14+
class ViewModel: ObservableObject {
15+
16+
let api: UrApiServiceProtocol
17+
@Published private(set) var isLoading: Bool = false
18+
@Published var displayRedeemSheet: Bool = false
19+
@Published private(set) var isInitializing: Bool = true
20+
@Published private(set) var redeemedBalanceCodes: [SdkRedeemedBalanceCode] = []
21+
22+
init(
23+
api: UrApiServiceProtocol
24+
) {
25+
self.api = api
26+
27+
Task {
28+
await self.getRedeemedBalanceCodes()
29+
self.isInitializing = false
30+
}
31+
}
32+
33+
func getRedeemedBalanceCodes() async {
34+
35+
if (isLoading) {
36+
return
37+
}
38+
39+
isLoading = true
40+
41+
do {
42+
43+
let result = try await api.getRedeemedBalanceCodes()
44+
45+
let len = result.balanceCodes?.len() ?? 0
46+
var balanceCodes: [SdkRedeemedBalanceCode] = []
47+
48+
if len > 0 {
49+
50+
// loop
51+
for i in 0..<len {
52+
53+
if let location = result.balanceCodes?.get(i) {
54+
balanceCodes.append(location)
55+
}
56+
}
57+
}
58+
59+
self.redeemedBalanceCodes = balanceCodes
60+
self.isLoading = false
61+
62+
} catch (let error) {
63+
print("error fetching redeemed balance codes: \(error)")
64+
self.isLoading = false
65+
}
66+
67+
}
68+
69+
}
70+
71+
}

0 commit comments

Comments
 (0)