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