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 }