BalancesSectionView.swift (9398B)
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 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 let balanceDest = TransactionsListView(stack: stack.push("\(Self.className())()"), 103 scope: scopeInfo, 104 balance: balance, 105 selectedBalance: $selectedBalance, 106 navTitle: balance.available.readableDescription, // TODO: format with currency sign 107 oimEuro: oimEuro, 108 transactions: $completedTransactions, 109 reloadAllAction: loadCompleted) 110 Section { 111 if scopeInfo.type == .exchange { 112 let baseURL = scopeInfo.url?.trimURL ?? String(localized: "Unknown payment service", comment: "exchange url") 113 Text(baseURL) 114 .talerFont(.subheadline) 115 .foregroundColor(.secondary) 116 // .listRowSeparator(.hidden) 117 } 118 BalanceCellV(stack: stack.push("BalanceCell"), 119 scope: balance.scopeInfo, 120 amount: balance.available, 121 historyTapped: $historyTapped, 122 balanceIndex: balanceIndex, 123 balanceDest: balanceDest) 124 // .listRowSeparator(.hidden) 125 // .border(.red) 126 127 if !pendingTransactions.isEmpty { 128 BalancesPendingRowV(//symLog: symLog, 129 stack: stack.push(), 130 balance: balance, 131 selectedBalance: $selectedBalance, 132 pendingTransactions: $pendingTransactions, 133 reloadPending: loadPending) 134 .padding(.leading, ICONLEADING) 135 } 136 if !scannedTransactions.isEmpty { 137 BalancesDialogRowV(stack: stack.push(), 138 balance: balance, 139 selectedBalance: $selectedBalance, 140 scannedTransactions: $scannedTransactions, 141 reloadScanned: loadScanned) 142 .padding(.leading, ICONLEADING) 143 } 144 } header: { 145 BarGraphHeader(stack: stack.push(), 146 scope: scopeInfo, 147 reloadTransactions: $reloadTransactions) 148 }.id(sectionID) 149 .listRowSeparator(.hidden) 150 .task(id: reloadTransactions + 1_000_000) { 151 symLog.log(".task for BalancesSectionView - load recent+completed+pending") 152 await loadRecent(stack.push(".task - load recent")) 153 await loadCompleted(stack.push(".task - load completed")) // TODO: only in TX list view 154 await loadPending(stack.push(".task - load pending")) 155 await loadScanned(stack.push(".task - load scanned")) 156 } 157 158 /// if there is only one currency, then show MAXRECENT recent transactions 159 let recentCount = recentTransactions.count 160 if sectionCount == 1 && recentCount > 0 { 161 let _ = symLog.log("recent transactions") 162 let recentHeader = minimalistic ? nil 163 : recentCount > 1 164 ? String(localized: "Recent transactions", comment: "section header plural") 165 : String(localized: "Recent transaction", comment: "section header singular") 166 TransactionsArraySection(symLog: symLog, 167 logger: logger, 168 stack: stack.push(), 169 header: recentHeader, 170 scope: scopeInfo, 171 transactions: $recentTransactions, 172 reloadAllAction: loadRecent) 173 } // recent transactions 174 } // body 175 } // BalancesSectionView 176 // MARK: - 177 #if false // model crashes 178 struct BalancesSectionView_Previews: PreviewProvider { 179 fileprivate struct BindingViewContainer: View { 180 @State var amountToTransfer: UInt64 = 333 181 @State private var summary: String = "bla-bla" 182 183 var body: some View { 184 let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, url: DEMOEXCHANGE, currency: LONGCURRENCY) 185 let balance = Balance(scopeInfo: scopeInfo, 186 available: Amount(currency: LONGCURRENCY, cent:1), 187 hasPendingTransactions: true) 188 BalancesSectionView(balance: balance, 189 sectionCount: 2, 190 amountToTransfer: $amountToTransfer, 191 summary: $summary) 192 } 193 } 194 195 static var previews: some View { 196 List { 197 BindingViewContainer() 198 } 199 } 200 } 201 #endif