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 }