BalancesSectionView.swift (9713B)
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 let isLegacy = scopeInfo.type == .exchangeLegacyKeys 111 Section { 112 if scopeInfo.type == .exchange || isLegacy { 113 let baseURL = scopeInfo.url?.trimURL ?? String(localized: "Unknown payment service", comment: "exchange url") 114 Text(baseURL) 115 .talerFont(.subheadline) 116 .foregroundColor(.secondary) 117 // .listRowSeparator(.hidden) 118 } 119 BalanceCellV(stack: stack.push("BalanceCell"), 120 scope: balance.scopeInfo, 121 amount: balance.available, 122 isLegacy: isLegacy, 123 historyTapped: $historyTapped, 124 balanceIndex: balanceIndex, 125 balanceDest: balanceDest) 126 // .listRowSeparator(.hidden) 127 // .border(.red) 128 129 if !pendingTransactions.isEmpty { 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.isEmpty { 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 sectionCount == 1 { 160 // show Discounts & Passes before recents 161 DiscountPassesSection(stack: stack.push()) 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