ExchangeListView.swift (6547B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import SwiftUI 9 import taler_swift 10 import SymLog 11 12 @MainActor 13 fileprivate 14 func addExchange(_ exchange: String, model: WalletModel) -> Void { 15 Task { // runs on MainActor 16 if let _ = try? await model.addExchange(uri: exchange) { 17 // symLog.log("added: \(exchange)") 18 // View.announce("added: \(exchange)") 19 if UIAccessibility.isVoiceOverRunning { 20 UIAccessibility.post(notification: .announcement, argument: "added: \(exchange)") 21 } 22 NotificationCenter.default.post(name: .ExchangeAdded, object: nil, userInfo: nil) 23 NotificationCenter.default.post(name: .BalanceChange, object: nil, userInfo: nil) // TODO: ExchangeAdded should suffice 24 } 25 } 26 } 27 28 /// This view shows the list of exchanges 29 struct ExchangeListView: View { 30 private let symLog = SymLogV(0) 31 let stack: CallStack 32 let navTitle: String 33 @EnvironmentObject private var model: WalletModel 34 @EnvironmentObject private var controller: Controller 35 #if DEBUG 36 @AppStorage("developerMode") var developerMode: Bool = true 37 #else 38 @AppStorage("developerMode") var developerMode: Bool = false 39 #endif 40 41 @State var showAlert: Bool = false 42 @State var newExchange: String = TESTEXCHANGE 43 44 var body: some View { 45 let a11yLabelStr = String(localized: "Add payment service", comment: "a11y for the + button") 46 let plusButton = PlusButton(accessibilityLabelStr: a11yLabelStr) { 47 showAlert = true 48 } 49 let addTitleStr = String(localized: "Add payment service", comment: "title of the addExchange alert") 50 let addButtonStr = String(localized: "Add", comment: "button in the addExchange alert") 51 let enterURL = String(localized: "Enter the URL") 52 if #available(iOS 16.4, *) { 53 ExchangeListCommonV(symLog: symLog, stack: stack.push(), developerMode: developerMode) 54 .navigationTitle(navTitle) 55 .navigationBarItems(trailing: plusButton) 56 .alert(addTitleStr, isPresented: $showAlert) { 57 TextField("Address of the payment service", text: $newExchange) 58 .accessibilityLabel(enterURL) 59 // .textFieldStyle(.roundedBorder) Yikes: when adding style the alert will stop showing the textfield! Don't do this. 60 Button(addButtonStr) { 61 addExchange(newExchange, model: model) 62 } 63 Button("Cancel", role: .cancel) { } 64 } message: { 65 Text(enterURL) 66 .accessibilityHidden(true) 67 } 68 } else { // iOS 15 cannot have a textfield in an alert, so we must 69 ExchangeListCommonV(symLog: symLog, stack: stack.push(), developerMode: developerMode) 70 .navigationTitle(navTitle) 71 .navigationBarItems(trailing: plusButton) 72 .textFieldAlert(isPresented: $showAlert, 73 title: addTitleStr, 74 doneText: addButtonStr, 75 text: $newExchange) { text in 76 addExchange(text, model: model) 77 } 78 } 79 } 80 } 81 // MARK: - 82 struct ExchangeListCommonV: View { 83 let symLog: SymLogV? 84 let stack: CallStack 85 let developerMode: Bool 86 87 @EnvironmentObject private var controller: Controller 88 @EnvironmentObject private var model: WalletModel 89 @AppStorage("minimalistic") var minimalistic: Bool = false 90 @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic 91 92 var body: some View { 93 #if PRINT_CHANGES 94 let _ = Self._printChanges() 95 let _ = symLog?.vlog() // just to get the # to compare it with .onAppear & onDisappear 96 #endif 97 #if DEBUG 98 let excSection = Section { 99 let cyclos = "https://guava.box.fdold.eu" 100 Button(cyclos) { 101 addExchange(cyclos, model: model) 102 } 103 104 let exchanges = ["glsint.fdold.eu", "taler.magnetbank.hu", "stage.taler-ops.ch", "e.netzbon-basel.ch"] 105 ForEach(exchanges, id: \.self) { exchange in 106 let urlStr = "https://exchange." + exchange 107 Button(urlStr) { 108 addExchange(urlStr, model: model) 109 } 110 // Link(exchange, destination: URL(string: urlStr)!) 111 } 112 } 113 #endif 114 let balanceList = List { 115 ForEach(Array(controller.balances.enumerated()), id: \.1.id) { index, balance in 116 ExchangeSectionView(stack: stack.push(), 117 balance: balance, 118 thousand: index * MAXEXCHANGES, // unique ID 119 developerMode: developerMode) 120 } 121 #if DEBUG 122 if developerMode { 123 excSection 124 } 125 #endif 126 } 127 128 let emptyList = List { 129 Section { 130 Text("There are no payment services yet.") 131 .talerFont(.title3) 132 } 133 Section { 134 let plus = Image(systemName: PLUS) 135 if !minimalistic { 136 Text("Tap the \(plus) button to add a service.") 137 .listRowSeparator(.hidden) 138 Text("You can also scan a withdrawal QR code from your bank in the Action menu to automatically add a payment service.") 139 } else { 140 Text("Tap \(plus) or scan a withdrawal QR code from your bank to add a payment service.") 141 } 142 } 143 .talerFont(.body) 144 #if DEBUG 145 if developerMode { 146 excSection 147 } 148 #endif 149 } 150 151 Group { 152 if controller.balances.isEmpty { 153 emptyList 154 } else { 155 balanceList // sorted by wallet-core 156 } 157 } 158 .listStyle(myListStyle.style).anyView 159 .refreshable { 160 controller.hapticNotification(.success) 161 symLog?.log("refreshing") 162 // await reloadExchanges() 163 NotificationCenter.default.post(name: .BalanceChange, object: nil, userInfo: nil) 164 } 165 .onAppear() { 166 DebugViewC.shared.setViewID(VIEW_PAYMENT_SERVICES, stack: stack.push()) 167 } 168 } // body 169 }