taler-ios

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

ExchangeSectionView.swift (8073B)


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