taler-ios

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

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 }