taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

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 }