ExchangeListView.swift (6743B)
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 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 @Binding var url: URL? 33 34 @EnvironmentObject private var model: WalletModel 35 @EnvironmentObject private var controller: Controller 36 #if DEBUG 37 @AppStorage("developerMode") var developerMode: Bool = true 38 #else 39 @AppStorage("developerMode") var developerMode: Bool = false 40 #endif 41 42 @State var showAlert: Bool = false 43 @State var newExchange: String = TESTEXCHANGE 44 45 var body: some View { 46 let a11yLabelStr = String(localized: "Add payment service", comment: "a11y for the + button") 47 let plusButton = PlusButton(accessibilityLabelStr: a11yLabelStr) { 48 showAlert = true 49 } 50 let addTitleStr = String(localized: "Add payment service", comment: "title of the addExchange alert") 51 let addButtonStr = String(localized: "Add", comment: "button in the addExchange alert") 52 let enterURL = String(localized: "Enter the URL") 53 let listView = ExchangeListCommonV(symLog: symLog, stack: stack.push(), developerMode: developerMode) 54 .navigationTitle(TITLE_EXCHANGES) 55 .navigationBarItems(trailing: plusButton) 56 .task { 57 if let newUrl = url { 58 newExchange = newUrl.absoluteString 59 showAlert = true 60 url = nil 61 } 62 } 63 if #available(iOS 16.4, *) { 64 listView.alert(addTitleStr, isPresented: $showAlert) { 65 TextField("Address of the payment service", text: $newExchange) 66 .accessibilityLabel(enterURL) 67 // .textFieldStyle(.roundedBorder) Yikes: when adding style the alert will stop showing the textfield! Don't do this. 68 Button(addButtonStr) { 69 addExchange(newExchange, model: model) 70 } 71 Button("Cancel", role: .cancel) { } 72 } message: { 73 Text(enterURL) 74 .accessibilityHidden(true) 75 } 76 } else { // iOS 15 cannot have a textfield in an alert, so we must 77 listView 78 .textFieldAlert(isPresented: $showAlert, 79 title: addTitleStr, 80 doneText: addButtonStr, 81 text: $newExchange) { text in 82 addExchange(text, model: model) 83 } 84 } 85 } 86 } 87 // MARK: - 88 struct ExchangeListCommonV: View { 89 let symLog: SymLogV? 90 let stack: CallStack 91 let developerMode: Bool 92 93 @EnvironmentObject private var controller: Controller 94 @EnvironmentObject private var model: WalletModel 95 @AppStorage("minimalistic") var minimalistic: Bool = false 96 @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic 97 98 var body: some View { 99 #if PRINT_CHANGES 100 let _ = Self._printChanges() 101 let _ = symLog?.vlog() // just to get the # to compare it with .onAppear & onDisappear 102 #endif 103 #if DEBUG 104 let excSection = Section { 105 let cyclos = "https://guava.box.fdold.eu" 106 Button(cyclos) { 107 addExchange(cyclos, model: model) 108 } 109 110 let exchanges = ["glsint.fdold.eu", "taler.magnetbank.hu", "stage.taler-ops.ch", "e.netzbon-basel.ch"] 111 ForEach(exchanges, id: \.self) { exchange in 112 let urlStr = "https://exchange." + exchange 113 Button(urlStr) { 114 addExchange(urlStr, model: model) 115 } 116 // Link(exchange, destination: URL(string: urlStr)!) 117 } 118 } 119 #endif 120 let balanceList = List { 121 ForEachWithIndex(data: controller.balances) { index, balance in 122 ExchangeSectionView(stack: stack.push(), 123 balance: balance, 124 thousand: index * MAXEXCHANGES, // unique ID 125 developerMode: developerMode) 126 } 127 #if DEBUG 128 if developerMode { 129 excSection 130 } 131 #endif 132 } 133 134 let emptyList = List { 135 Section { 136 Text("There are no payment services yet.") 137 .talerFont(.title3) 138 } 139 Section { 140 let plus = Image(systemName: PLUS) 141 if !minimalistic { 142 Text("Tap the \(plus) button to add a service.") 143 .listRowSeparator(.hidden) 144 Text("You can also scan a withdrawal QR code from your bank in the Action menu to automatically add a payment service.") 145 } else { 146 Text("Tap \(plus) or scan a withdrawal QR code from your bank to add a payment service.") 147 } 148 } 149 .talerFont(.body) 150 #if DEBUG 151 if developerMode { 152 excSection 153 } 154 #endif 155 } 156 157 Group { 158 if controller.balances.isEmpty { 159 emptyList 160 } else { 161 balanceList // sorted by wallet-core 162 } 163 } 164 .listStyle(myListStyle.style).anyView 165 // .ignoresSafeArea(.keyboard, edges: .bottom) 166 .refreshable { 167 controller.hapticNotification(.success) 168 symLog?.log("refreshing") 169 // await reloadExchanges() 170 NotificationCenter.default.post(name: .BalanceChange, object: nil, userInfo: nil) 171 } 172 .onAppear() { 173 DebugViewC.shared.setViewID(VIEW_PAYMENT_SERVICES, stack: stack.push()) 174 } 175 } // body 176 }