taler-ios

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

ExchangeSectionView.swift (7968B)


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