BalancesSectionView.swift (9632B)
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("BalanceCellV"), 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 if sectionCount == 1 { 158 #if DEBUG || TALER_NIGHTLY 159 // show Discounts & Passes before recents 160 DiscountPassesSection(stack: stack.push()) 161 #endif 162 // if there is only one currency, then show MAXRECENT recent transactions 163 let recentCount = recentTransactions.count 164 if recentCount > 0 { 165 let _ = symLog.log("recent transactions") 166 let recentHeader = minimalistic ? nil 167 : recentCount > 1 168 ? String(localized: "Recent transactions", comment: "section header plural") 169 : String(localized: "Recent transaction", comment: "section header singular") 170 TransactionsArraySection(symLog: symLog, 171 logger: logger, 172 stack: stack.push(), 173 header: recentHeader, 174 scope: scopeInfo, 175 transactions: $recentTransactions, 176 reloadAllAction: loadRecent) 177 } // recent transactions 178 } // 1 Section only 179 } // body 180 } // BalancesSectionView 181 // MARK: - 182 #if false // model crashes 183 struct BalancesSectionView_Previews: PreviewProvider { 184 fileprivate struct BindingViewContainer: View { 185 @State var amountToTransfer: UInt64 = 333 186 @State private var summary: String = "bla-bla" 187 188 var body: some View { 189 let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, url: DEMOEXCHANGE, currency: LONGCURRENCY) 190 let balance = Balance(scopeInfo: scopeInfo, 191 available: Amount(currency: LONGCURRENCY, cent:1), 192 hasPendingTransactions: true) 193 BalancesSectionView(balance: balance, 194 sectionCount: 2, 195 amountToTransfer: $amountToTransfer, 196 summary: $summary) 197 } 198 } 199 200 static var previews: some View { 201 List { 202 BindingViewContainer() 203 } 204 } 205 } 206 #endif