WalletEmptyView.swift (12103B)
1 /* 2 * This file is part of GNU Taler, ©2022-26 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import SwiftUI 9 import SymLog 10 import taler_swift 11 12 struct ProdSectionView: View { 13 let stack: CallStack 14 @Binding var showRealCashHint: Bool 15 let isEmpty: Bool 16 let disabled: Bool 17 18 @Environment(\.colorScheme) private var colorScheme 19 @Environment(\.colorSchemeContrast) private var colorSchemeContrast 20 @EnvironmentObject private var controller: Controller 21 @EnvironmentObject private var model: WalletModel 22 #if DEBUG 23 @AppStorage("developerMode") var developerMode: Bool = true 24 #else 25 @AppStorage("developerMode") var developerMode: Bool = false 26 #endif 27 @AppStorage("preferredColorScheme") var preferredColorScheme: Int = 0 28 @AppStorage("minimalistic") var minimalistic: Bool = false 29 30 @State private var selectedCurrency: Int = 0 31 32 func buttonAction() { 33 withAnimation { 34 showRealCashHint.toggle() 35 } 36 } 37 38 var body: some View { 39 let defaultExchanges = controller.defaultExchanges 40 let count = defaultExchanges.count 41 let section = Section { 42 let chevron = Image(systemName: CHEVRON_UP) // 43 if isEmpty { 44 Text("Welcome to Taler Wallet!") 45 .talerFont(.headline) 46 Text("To make your first payment, withdraw digital cash.") 47 .talerFont(.body) 48 } else { 49 let withdraw = Text("Withdraw real digital cash:") 50 .talerFont(.body) 51 Button(action: buttonAction) { 52 HStack(alignment: .firstTextBaseline) { 53 if minimalistic { 54 withdraw 55 } else { 56 Text("Currently you have only demo cash.") 57 .talerFont(.body) 58 } 59 Spacer() 60 chevron.rotationEffect(.degrees((showRealCashHint ? -180 : 0))) 61 .accessibilityHidden(true) 62 } 63 .accessibilityElement(children: .combine) 64 .accessibilityHint(Text(showRealCashHint ? "Hide available real money withdrawals" 65 : "Show available real money withdrawals")) 66 .contentShape(.rect) // can tap in background also 67 } .buttonStyle(.plain) 68 if showRealCashHint && !minimalistic { 69 withdraw 70 .padding(.vertical, -16) 71 } 72 } 73 #if false 74 let qrButton = Image(systemName: QRBUTTON) // "qrcode.viewfinder" 75 let settings = Image(systemName: SETTINGS) // "gear" 76 Text("Use «\(qrButton) Scan QR code» in the Actions menu to start a withdrawal if your bank already supports Taler payments.", comment: "« » 'qrcode.viewfinder'") 77 .talerFont(.body) 78 Text("You can also add a payment service manually in the \(settings) Settings tab.", comment: "« » 'gear'") 79 .talerFont(.body) 80 #endif 81 if (showRealCashHint) { 82 if developerMode { 83 if defaultExchanges.count > 1 { 84 CurrencyPicker(stack: stack.push(), value: $selectedCurrency, 85 exchanges: defaultExchanges) { index in 86 selectedCurrency = index 87 } 88 } 89 } 90 let defaultExchange = defaultExchanges[selectedCurrency < count ? selectedCurrency : 0] 91 let currency = defaultExchange.currency 92 let title = String(localized: "LinkTitle_Withdraw", defaultValue: "Withdraw \(currency)") 93 Button(title) { 94 if let talerURI = URL(string: defaultExchange.talerUri) { 95 controller.talerURI = talerURI 96 } 97 } 98 .buttonStyle(TalerButtonStyle(type: isEmpty ? .prominent : .bordered, 99 narrow: false, 100 disabled: disabled, 101 aligned: .center)) 102 .padding(.top, -6) 103 .padding(.bottom, 6) 104 .accessibilityIdentifier(A11yID.withdrawRealButton) 105 } // showDropdown 106 } header: { 107 if isEmpty { 108 let firstHeader = EMPTYSTRING 109 Text(firstHeader) 110 .talerFont(.badge) 111 // .foregroundColor(.primary) 112 /// YIKES! when not specifying this color, the Text in the BarGraphHeader vanishes!!! 113 .foregroundColor(WalletColors().secondary2(colorScheme, colorSchemeContrast)) 114 // .listRowInsets(EdgeInsets()) // << here !! 115 } 116 } 117 .listRowSeparator(.hidden) 118 // .listSectionSpacing(.compact) iOS 17 119 120 if preferredColorScheme == 3, #available(iOS 17.0, *) { 121 section.listRowBackground(Color.white.opacity(0.1)) 122 } else { 123 section 124 } 125 } 126 } 127 // MARK: - 128 /// This view shows hints if a wallet is empty 129 /// It is the very first thing the user sees after installing the app 130 struct WalletEmptyHeader: View { 131 let stack: CallStack 132 @Binding var showRealCashHint: Bool 133 134 @EnvironmentObject private var controller: Controller 135 136 func refresh() async { 137 controller.hapticNotification(.success) 138 // symLog.log("refreshing balances") 139 await controller.loadBalances(stack.push("refreshing balances")) 140 } 141 142 var body: some View { 143 let emptyView = WalletEmptyView(stack: stack.push("isEmpty"), 144 showRealCashHint: $showRealCashHint) 145 // .navigationTitle("Taler Wallet") 146 .refreshable { 147 await refresh() 148 } 149 WalletHeader(showSettings: true, content: emptyView) 150 } 151 } 152 // MARK: - 153 struct WalletHeader<Content: View> : View { 154 let showSettings: Bool 155 let content: Content 156 157 @Environment(\.accessibilityVoiceOverEnabled) private var voiceOverEnabled 158 159 var body: some View { 160 let talerLogo = HStack(spacing: 2) { 161 Image(TALER_LOGO_FULL) 162 // .border(Color.gray, width: 1) 163 // Text("TALER Wallet") 164 Text("Wallet") 165 // .font(.logo(27, weight: .medium)) 166 .font(.logo("Atkinson Hyperlegible Next", size: 27, weight: .medium)) 167 .kerning(1.0) 168 .fixedSize(horizontal: true, vertical: true) 169 } 170 .accessibilityElement(children: .combine) 171 .accessibilityAddTraits(.isHeader) 172 .accessibilityLabel(Text("Taler Wallet", comment: "a11y")) 173 174 175 let logoItem = ToolbarItem(placement: .principal) { 176 talerLogo 177 .padding(.top, 10) 178 } 179 if showSettings, #available(iOS 26.0, *) { 180 if voiceOverEnabled { 181 content 182 .toolbar { logoItem } 183 } else { 184 content 185 .toolbar { 186 ToolbarItem(placement: .principal) { 187 HStack { 188 SettingsButton26(sortPriority: 0) 189 .hidden() 190 Spacer() 191 talerLogo 192 .padding(.top, 10) 193 Spacer() 194 SettingsButton26(sortPriority: 0) 195 .offset(x: 16, y: 0) 196 } 197 } 198 } 199 } 200 } else { // iOS 15..18 Settings is the 3rd tab 201 content 202 .toolbar { logoItem } 203 } 204 } 205 } 206 // MARK: - 207 struct WalletEmptyView: View { 208 private let symLog = SymLogV(0) 209 let stack: CallStack 210 @Binding var showRealCashHint: Bool 211 212 @EnvironmentObject private var controller: Controller 213 @EnvironmentObject private var model: WalletModel 214 @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic 215 @AppStorage("preferredColorScheme") var preferredColorScheme: Int = 0 216 @State private var withDrawStarted = false 217 @State private var urlToOpen: URL? = nil 218 219 var body: some View { 220 let list = List { 221 if !controller.defaultExchanges.isEmpty { 222 ProdSectionView(stack: stack.push(), 223 showRealCashHint: $showRealCashHint, 224 isEmpty: true, disabled: withDrawStarted) 225 } 226 #if GNU_TALER 227 Text("If you don't have a Swiss bank account, you can simply try out the demo…") 228 .talerFont(.body) 229 #endif 230 231 #if DEBUG 232 // if developerMode { 233 // Text("Either deposit or send your money to a friend before you delete the app, or it will be lost.") 234 // .talerFont(.body) 235 // } 236 #endif 237 let section = Section { 238 // Text("Demo") 239 // .talerFont(.headline) 240 // Text("Get digital cash to experience how easy you can pay online, and send money to friends & family.") 241 Text("Get demo cash to experience how to pay with digital cash.") 242 // Text("Get digital cash to experience how to pay with the money of the future.") 243 .talerFont(.body) 244 .listRowSeparator(.hidden) 245 let title = String(localized: "LinkTitle_Test_Money", defaultValue: "Get demo cash") 246 Button(title) { 247 withDrawStarted = true // don't run twice 248 Task { // runs on MainActor 249 let amount = Amount(currency: DEMOCURRENCY, cent: DEMOAMOUNT) 250 symLog.log("Withdraw 25 KUDOS") 251 try? await model.loadTestKudos(0, amount: amount) 252 DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 253 withDrawStarted = false 254 } 255 } 256 } 257 .buttonStyle(TalerButtonStyle(type: .prominent, narrow: false, disabled: withDrawStarted, aligned: .center)) 258 .disabled(withDrawStarted) 259 .accessibilityIdentifier(A11yID.demoCashButton) 260 // } header: { 261 // let secondHeader = String(localized: "Demo", comment: "section header") 262 // Text(secondHeader) 263 // .talerFont(.title3) 264 // .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast)) 265 } 266 if preferredColorScheme == 3, #available(iOS 17.0, *) { 267 section.listRowBackground(Color.white.opacity(0.1)) 268 } else { 269 section 270 } 271 } 272 .listStyle(myListStyle.style).anyView 273 .talerFont(.title2) 274 .background(FullBackground()) 275 ZStack { 276 if preferredColorScheme == 3, #available(iOS 17.0, *) { 277 list.scrollContentBackground(.hidden) 278 } else { 279 list 280 } 281 if withDrawStarted { 282 RotatingTaler(size: 150, progress: true, once: false, 283 background: nil, 284 rotationEnabled: $withDrawStarted) 285 } 286 } 287 .onAppear() { 288 DebugViewC.shared.setViewID(VIEW_EMPTY_WALLET, stack: stack.push("onAppear")) // 10 289 } 290 } 291 } 292 // MARK: - 293 struct WalletEmptyView_Previews: PreviewProvider { 294 static var previews: some View { 295 WalletEmptyView(stack: CallStack("Preview"), 296 showRealCashHint: Binding.constant(true)) 297 } 298 }