TransactionsListView.swift (8792B)
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 SymLog 11 12 #if DEBUG 13 fileprivate let showUpDown = 8 // show up+down buttons in the menubar if list has many lines 14 #else 15 fileprivate let showUpDown = 25 // show up+down buttons in the menubar if list has many lines 16 #endif 17 struct TransactionsListView: View { 18 private let symLog = SymLogV(0) 19 let stack: CallStack 20 let scope: ScopeInfo 21 let balance: Balance // this is the currency to be used 22 @Binding var selectedBalance: Balance? // <- return here the balance when we go to Transactions 23 let navTitle: String? 24 25 @Binding var transactions: [TalerTransaction] 26 27 let reloadAllAction: (_ stack: CallStack) async -> () 28 29 let logger = Logger(subsystem: "net.taler.gnu", category: "TransactionsList") 30 @EnvironmentObject private var controller: Controller 31 @Environment(\.colorScheme) private var colorScheme 32 @Environment(\.colorSchemeContrast) private var colorSchemeContrast 33 @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic 34 @State private var viewId = UUID() 35 @StateObject private var cash: OIMcash 36 @Namespace var namespace 37 38 init(stack: CallStack, 39 scope: ScopeInfo, 40 balance: Balance, 41 selectedBalance: Binding<Balance?>, 42 navTitle: String?, 43 oimEuro: Bool, 44 transactions: Binding<[TalerTransaction]>, 45 reloadAllAction: @escaping (_ stack: CallStack) async -> () 46 ) { 47 // SwiftUI ensures that the initialization uses the 48 // closure only once during the lifetime of the view, so 49 // later changes to the currency have no effect. 50 self.stack = stack 51 self.scope = scope 52 self.balance = balance 53 self.navTitle = navTitle 54 self._transactions = transactions 55 self.reloadAllAction = reloadAllAction 56 self._selectedBalance = selectedBalance 57 let oimCurrency = oimCurrency(balance.scopeInfo, oimEuro: oimEuro) 58 let oimCash = OIMcash(oimCurrency) 59 self._cash = StateObject(wrappedValue: { oimCash }()) 60 } 61 var body: some View { 62 #if PRINT_CHANGES 63 let _ = Self._printChanges() 64 let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear 65 #endif 66 if transactions.isEmpty { 67 TransactionsEmptyView(stack: stack.push(), currency: scope.currency) 68 .refreshable { 69 controller.hapticNotification(.success) 70 symLog.log("refreshing") 71 await reloadAllAction(stack.push()) 72 } 73 } else { 74 // Group { 75 ScrollViewReader { scrollProxy in 76 let count = transactions.count 77 List { 78 Section { 79 TransactionsArraySliceV(symLog: symLog, 80 logger: logger, 81 stack: stack.push(), 82 scope: scope, 83 transactions: $transactions, 84 reloadAllAction: reloadAllAction) 85 .padding(.leading, ICONLEADING) 86 } header: { 87 let header = scope.url?.trimURL ?? scope.currency 88 Text(header) 89 .talerFont(.title3) 90 .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast)) 91 } 92 } 93 .id(viewId) 94 .listStyle(myListStyle.style).anyView 95 .refreshable { 96 controller.hapticNotification(.success) 97 symLog.log("refreshing") 98 await reloadAllAction(stack.push()) 99 } 100 #if false // SCROLLBUTTONS 101 .if(count > showUpDown) { view in 102 view.navigationBarItems(trailing: HStack { 103 ArrowUpButton { 104 // print("up") 105 withAnimation { scrollProxy.scrollTo(0) } 106 } 107 ArrowDownButton { 108 // print("down") 109 withAnimation { scrollProxy.scrollTo(transactions.count - 1) } 110 } 111 }) 112 } 113 #endif 114 } // ScrollViewReader 115 // .navigationTitle("EURO") // Fake EUR instead of the real Currency 116 // .navigationTitle("CHF") // Fake CHF instead of the real Currency 117 .navigationTitle(navTitle ?? scope.currency) 118 .accessibilityHint(String(localized: "Transaction list", comment: "a11y")) 119 .task { 120 symLog.log("❗️.task List❗️") 121 await reloadAllAction(stack.push()) 122 } 123 .onAppear { 124 DebugViewC.shared.setViewID(VIEW_TRANSACTIONLIST, stack: stack.push()) 125 print("🚩,32TransactionsListView.onAppear() set selectedBalance to", balance.scopeInfo.currency) 126 selectedBalance = balance // set this balance (fix) for send/request/deposit/withdraw 127 } 128 // } 129 #if OIM 130 .overlay { if #available(iOS 16.4, *) { 131 if controller.oimModeActive { 132 OIMtransactions(stack: stack.push(), 133 balance: balance, 134 cash: cash, 135 history: transactions) 136 .environmentObject(NamespaceWrapper(namespace)) // keep OIMviews apart 137 } 138 } } 139 #endif 140 } // not empty 141 } // body 142 } 143 // MARK: - 144 // used by TransactionsListView, and by BalancesSectionView to show the last 3 transactions 145 struct TransactionsArraySliceV: View { 146 let symLog: SymLogV? 147 let logger: Logger? 148 let stack: CallStack 149 let scope: ScopeInfo 150 @Binding var transactions: [TalerTransaction] 151 let reloadAllAction: (_ stack: CallStack) async -> () 152 153 @EnvironmentObject private var model: WalletModel 154 @State private var talerTX: TalerTransaction = TalerTransaction(dummyCurrency: DEMOCURRENCY) 155 156 var body: some View { 157 #if PRINT_CHANGES 158 let _ = Self._printChanges() 159 let _ = symLog?.vlog() // just to get the # to compare it with .onAppear & onDisappear 160 #endif 161 let abortAction = model.abortTransaction 162 let deleteAction = model.deleteTransaction 163 let failAction = model.failTransaction 164 let suspendAction = model.suspendTransaction 165 let resumeAction = model.resumeTransaction 166 167 ForEach(transactions, id: \.self) { transaction in 168 let destination = TransactionSummaryV(stack: stack.push(), 169 // scope: scope, 170 transactionId: transaction.id, 171 talerTX: $talerTX, 172 navTitle: nil, 173 hasDone: false, 174 abortAction: abortAction, 175 deleteAction: deleteAction, 176 failAction: failAction, 177 suspendAction: suspendAction, 178 resumeAction: resumeAction) 179 let row = NavigationLink { destination } label: { 180 TransactionRowView(logger: logger, scope: scope, transaction: transaction) 181 }.id(transaction.id) 182 if transaction.isDeleteable { 183 row.swipeActions(edge: .trailing) { 184 Button { 185 symLog?.log("deleteAction") 186 Task { // runs on MainActor 187 let _ = try? await deleteAction(transaction.id, false) 188 await reloadAllAction(stack.push()) 189 } 190 } label: { 191 Label("Delete", systemImage: "trash") 192 } 193 .tint(WalletColors().negative) 194 } 195 } else { 196 row 197 } 198 } 199 } 200 }