taler-ios

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

ExchangeSectionView.swift (7997B)


      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 /// This view shows the currency name in an exchange section
     13 ///         currency
     14 struct ExchangeSectionView: View {
     15     private let symLog = SymLogV(0)
     16     let stack: CallStack
     17     let balance: Balance
     18     let thousand: Int           // index of balance times 1000 (MAXEXCHANGES per currency)
     19     let developerMode: Bool
     20 
     21     @EnvironmentObject private var model: WalletModel
     22     @EnvironmentObject private var controller: Controller
     23     @AppStorage("minimalistic") var minimalistic: Bool = false
     24     @AppStorage("demoHints") var demoHints: Bool = true
     25     @AppStorage("fakeNoFees") var fakeNoFees: Bool = true
     26 
     27     @State private var exchanges: [Exchange] = []
     28     @State private var reloadTransactions: Int = 0
     29     @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN)
     30     @State private var didDelete: Bool = false
     31     @State private var disabled: Bool = false
     32     @State private var showAlert: Bool = false
     33     @State private var purge: Bool = false
     34     @State private var global: Bool = false
     35 
     36     @MainActor
     37     private func viewDidLoad() async {
     38         if let exc = try? await model.listExchanges(scope: balance.scopeInfo) {
     39             if exc.count > 0 {
     40                 let exchange = exc[0]
     41                 global = exchange.scopeInfo.type == .global
     42             }
     43             withAnimation { exchanges = exc }
     44         }
     45     }
     46 
     47     @MainActor
     48     private func currencyTickerChanged(_ scopeInfo: ScopeInfo) async {
     49         symLog.log("task \(didDelete ? 1 : 0)")
     50         currencyInfo = controller.info(for: scopeInfo, controller.currencyTicker)
     51     }
     52 
     53     @MainActor
     54     private func deleteExchange() {
     55         disabled = true     // don't try this more than once
     56         Task { // runs on MainActor
     57           if exchanges.count > 0 {
     58             let exchange = exchanges[0]
     59             let baseUrl = exchange.exchangeBaseUrl
     60             if let _ = try? await model.deleteExchange(url: baseUrl, purge: purge, viewHandles: !purge) {
     61                 purge = false
     62                 symLog.log("deleted \(baseUrl.trimURL)")
     63                 didDelete = true             // change button text
     64                 NotificationCenter.default.post(name: .ExchangeDeleted, object: nil, userInfo: nil)
     65                 NotificationCenter.default.post(name: .BalanceChange, object: nil, userInfo: nil)
     66                 demoHints = true
     67             } else {
     68                 purge = true
     69                 showAlert = true
     70                 disabled = false
     71             }
     72           }
     73         }
     74     }
     75 
     76     @MainActor
     77     private func setGlobal() {
     78         if exchanges.count > 0 {
     79             let exchange = exchanges[0]
     80             let scope = exchange.scopeInfo
     81             Task {
     82                 if scope.type != .global {
     83                     try? await model.addGlobalCurrencyExchange(currency: exchange.scopeInfo.currency,
     84                                                                 baseUrl: exchange.exchangeBaseUrl,
     85                                                               masterPub: exchange.masterPub ?? EMPTYSTRING)
     86                 } else {
     87                     try? await model.rmvGlobalCurrencyExchange(currency: exchange.scopeInfo.currency,
     88                                                                 baseUrl: exchange.exchangeBaseUrl,
     89                                                               masterPub: exchange.masterPub ?? EMPTYSTRING)
     90                 }
     91             }
     92         }
     93     }
     94     var body: some View {
     95 #if PRINT_CHANGES
     96         let _ = Self._printChanges()
     97 //        let _ = symLog.vlog()       // just to get the # to compare it with .onAppear & onDisappear
     98 #endif
     99         let scopeInfo = balance.scopeInfo
    100         let currency = scopeInfo.currency
    101         Section {
    102             ForEachWithIndex(data: exchanges) { index, exchange in
    103                 let _ = symLog.log("Index: \(thousand + index)")
    104                 ExchangeRowView(stack: stack.push(),
    105                              exchange: exchange,
    106                                 index: thousand + index + 1,
    107                         developerMode: developerMode)
    108                 .listRowSeparator(.hidden)
    109             }
    110             if developerMode && (DEMOCURRENCY == currency || TESTCURRENCY == currency) {
    111                 SettingsToggle(name: String("Global"), value: $global.onChange({ isGlobal in
    112                     setGlobal()
    113                 }),             id1: "global",
    114                         description: String("Treat this as a global exchange"))
    115                 .listRowSeparator(.hidden)
    116                 SettingsToggle(name: String("Fake no fees"), value: $fakeNoFees,
    117                                 id1: "fakeNoFees",
    118                         description: String("Remove fee and gros from details"))
    119             }
    120             if DEMOCURRENCY == currency {
    121                 SettingsToggle(name: String(localized: "Demo Hints"), value: $demoHints,
    122                                 id1: "demoHints",
    123                         description: String(localized: "Show hints for demo money"))
    124                 .listRowSeparator(.hidden)
    125                 let bankingHint = String(localized: "Since the demo bank supports the Taler integration, you can start a withdrawal directly on the")
    126                 let linkTitle = String(localized: "LinkTitle_DEMOBANK", defaultValue: "Demo Bank Website")
    127                 if demoHints {
    128                     VStack {
    129                         if !minimalistic {
    130                             Text(bankingHint)
    131                         }
    132                         Link(linkTitle, destination: URL(string: DEMOBANK)!)
    133                             .buttonStyle(TalerButtonStyle(type: .bordered, narrow: false, aligned: .center))
    134                     }
    135                     .accessibilityElement(children: .combine)
    136                     .accessibilityLabel(bankingHint + SPACE + linkTitle)
    137                     .padding(.top)
    138                     .listRowSeparator(.hidden)
    139                 }
    140             }
    141             let dialect = exchanges.first?.bankComplianceLanguage
    142             let buttonTitle = localizedString(.deleteExchange, forDialect: dialect)
    143             let warningText1 = localizedString(.warningText1, forDialect: dialect)
    144             let warningText2 = localizedString(.warningText2, forDialect: dialect)
    145             let warningText3 = localizedString(.warningText3, forDialect: dialect, currency)
    146             WarningButton(warningText: warningText1,
    147                           buttonTitle: buttonTitle,
    148                            buttonIcon: "trash",
    149                                  role: .destructive,       // optional: use WalletColors().errorColor
    150                              disabled: $disabled,
    151                                action: deleteExchange)
    152             .padding(.top)
    153             .alert(warningText2, isPresented: $showAlert, actions: {
    154                 Button("Cancel", role: .cancel) {
    155                     showAlert = false
    156                 }
    157                 Button("Deposit") {
    158                     showAlert = false
    159 //                    dismissTop(stack.push())              don't do this - will dismiss the Deposit view
    160                     NotificationCenter.default.post(name: .DepositAction, object: nil)  // will trigger NavigationLink
    161                 }
    162                 Button(buttonTitle) {
    163                     deleteExchange()
    164                     showAlert = false
    165                 }
    166             }, message: {
    167                 Text(warningText3)
    168             })
    169         } header: {
    170             BarGraphHeader(stack: stack.push(),
    171                            scope: scopeInfo,
    172               reloadTransactions: $reloadTransactions)
    173         }
    174         .task { await viewDidLoad() }
    175         .task(id: controller.currencyTicker) { await currencyTickerChanged(scopeInfo) }
    176         .onDisappear() {
    177             disabled = false
    178             purge = false
    179         }
    180     }
    181 }