BalancesSectionView.swift (10006B)
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 os.log 10 import taler_swift 11 import SymLog 12 13 /// This view shows a currency section 14 /// Currency Name 15 /// "Balance" $balance // leads to completed Transactions (.done) 16 /// optional: Pending Incoming 17 /// optional: Pending Outgoing 18 /// optional?: Suspended / Aborting / Aborted / Expired 19 struct BalancesSectionView { 20 private let symLog = SymLogV(0) 21 let stack: CallStack 22 let balance: Balance // this is the currency to be used 23 @Binding var selectedBalance: Balance? // <- return here the balance when we go to Transactions 24 let balanceIndex: Int 25 let sectionCount: Int 26 @Binding var amountToTransfer: Amount // does still have the wrong currency 27 @Binding var summary: String 28 @Binding var historyTapped: Int? 29 @Binding var reloadTransactions: Int 30 31 let logger = Logger(subsystem: "net.taler.gnu", category: "RecentList") 32 @EnvironmentObject private var model: WalletModel 33 @Environment(\.colorScheme) private var colorScheme 34 @Environment(\.colorSchemeContrast) private var colorSchemeContrast 35 @EnvironmentObject private var controller: Controller 36 #if DEBUG 37 @AppStorage("developerMode") var developerMode: Bool = true 38 #else 39 @AppStorage("developerMode") var developerMode: Bool = false 40 #endif 41 @AppStorage("minimalistic") var minimalistic: Bool = false 42 @AppStorage("oimEuro") var oimEuro: Bool = false 43 44 @State private var showSpendingHint = true 45 @State private var isShowingDetailView = false 46 @State private var completedTransactions: [TalerTransaction] = [] 47 @State private var recentTransactions: [TalerTransaction] = [] 48 @State private var pendingTransactions: [TalerTransaction] = [] 49 @State private var scannedTransactions: [TalerTransaction] = [] 50 51 private static func className() -> String {"\(self)"} 52 53 @State private var sectionID = UUID() 54 @State private var shownSectionID = UUID() // guaranteed to be different the first time 55 56 @MainActor 57 func loadRecent(_ stack: CallStack) async -> () { 58 if let transactions = try? await model.getTransactionsV2(stack.push("loadRecent - \(balance.scopeInfo.url?.trimURL)"), 59 scope: balance.scopeInfo, 60 filterByState: .final, 61 limit: MAXRECENT, 62 includeRefreshes: false) { 63 withAnimation { recentTransactions = transactions } 64 } 65 } 66 @MainActor 67 func loadCompleted(_ stack: CallStack) async -> () { 68 if let transactions = try? await model.getTransactionsV2(stack.push("loadCompleted - \(balance.scopeInfo.url?.trimURL)"), 69 scope: balance.scopeInfo, 70 filterByState: .final, 71 includeRefreshes: developerMode) { 72 withAnimation { completedTransactions = transactions } 73 } 74 } 75 @MainActor 76 func loadPending(_ stack: CallStack) async -> () { 77 if let transactions = try? await model.getTransactionsV2(stack.push("loadPending - \(balance.scopeInfo.url?.trimURL)"), 78 scope: balance.scopeInfo, 79 filterByState: .nonfinalApproved, 80 includeRefreshes: developerMode) { 81 withAnimation { pendingTransactions = transactions } 82 } 83 } 84 @MainActor 85 func loadScanned(_ stack: CallStack) async -> () { 86 if let transactions = try? await model.getTransactionsV2(stack.push("loadScanned - \(balance.scopeInfo.url?.trimURL)"), 87 scope: balance.scopeInfo, 88 filterByState: .nonfinalDialog, 89 includeRefreshes: developerMode) { 90 withAnimation { scannedTransactions = transactions } 91 } 92 } 93 } 94 95 extension BalancesSectionView: View { 96 var body: some View { 97 #if PRINT_CHANGES 98 let _ = Self._printChanges() 99 let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear 100 #endif 101 let scopeInfo = balance.scopeInfo 102 103 Group { 104 let balanceDest = TransactionsListView(stack: stack.push("\(Self.className())()"), 105 scope: scopeInfo, 106 balance: balance, 107 selectedBalance: $selectedBalance, 108 navTitle: balance.available.readableDescription, // TODO: format with currency sign 109 oimEuro: oimEuro, 110 transactions: $completedTransactions, 111 reloadAllAction: loadCompleted) 112 Section { 113 if scopeInfo.type == .exchange { 114 let baseURL = scopeInfo.url?.trimURL ?? String(localized: "Unknown payment service", comment: "exchange url") 115 Text(baseURL) 116 .talerFont(.subheadline) 117 .foregroundColor(.secondary) 118 // .listRowSeparator(.hidden) 119 } 120 BalanceCellV(stack: stack.push("BalanceCell"), 121 scope: balance.scopeInfo, 122 amount: balance.available, 123 historyTapped: $historyTapped, 124 balanceIndex: balanceIndex, 125 balanceDest: balanceDest) 126 // .listRowSeparator(.hidden) 127 // .border(.red) 128 129 if pendingTransactions.count > 0 { 130 BalancesPendingRowV(//symLog: symLog, 131 stack: stack.push(), 132 balance: balance, 133 selectedBalance: $selectedBalance, 134 pendingTransactions: $pendingTransactions, 135 reloadPending: loadPending) 136 .padding(.leading, ICONLEADING) 137 } 138 if scannedTransactions.count > 0 { 139 BalancesDialogRowV(stack: stack.push(), 140 balance: balance, 141 selectedBalance: $selectedBalance, 142 scannedTransactions: $scannedTransactions, 143 reloadScanned: loadScanned) 144 .padding(.leading, ICONLEADING) 145 } 146 } header: { 147 BarGraphHeader(stack: stack.push(), 148 scope: scopeInfo, 149 reloadTransactions: $reloadTransactions) 150 }.id(sectionID) 151 .listRowSeparator(.hidden) 152 .task(id: reloadTransactions + 1_000_000) { 153 symLog.log(".task for BalancesSectionView - load recent+completed+pending") 154 await loadRecent(stack.push(".task - load recent")) 155 await loadCompleted(stack.push(".task - load completed")) // TODO: only in TX list view 156 await loadPending(stack.push(".task - load pending")) 157 await loadScanned(stack.push(".task - load scanned")) 158 } 159 /// if there is only one currency, then show MAXRECENT recent transactions 160 if sectionCount == 1 && recentTransactions.count > 0 { 161 Section { 162 let _ = symLog.log("recent transactions") 163 TransactionsArraySliceV(symLog: symLog, 164 logger: logger, 165 stack: stack.push(), 166 scope: scopeInfo, 167 transactions: $recentTransactions, 168 reloadAllAction: loadRecent) 169 .padding(.leading, ICONLEADING) 170 } header: { 171 if !minimalistic { 172 let recentHeader = recentTransactions.count > 1 173 ? String(localized: "Recent transactions", comment: "section header plural") 174 : String(localized: "Recent transaction", comment: "section header singular") 175 Text(recentHeader) 176 .talerFont(.title3) 177 .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast)) 178 } 179 } 180 } // recent transactions 181 } 182 } // body 183 } // BalancesSectionView 184 // MARK: - 185 #if false // model crashes 186 struct BalancesSectionView_Previews: PreviewProvider { 187 fileprivate struct BindingViewContainer: View { 188 @State var amountToTransfer: UInt64 = 333 189 @State private var summary: String = "bla-bla" 190 191 var body: some View { 192 let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, url: DEMOEXCHANGE, currency: LONGCURRENCY) 193 let balance = Balance(scopeInfo: scopeInfo, 194 available: Amount(currency: LONGCURRENCY, cent:1), 195 hasPendingTransactions: true) 196 BalancesSectionView(balance: balance, 197 sectionCount: 2, 198 amountToTransfer: $amountToTransfer, 199 summary: $summary) 200 } 201 } 202 203 static var previews: some View { 204 List { 205 BindingViewContainer() 206 } 207 } 208 } 209 #endif