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 }