TransactionsListView.swift (8741B)
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 List { 77 Section { 78 TransactionsArraySliceV(symLog: symLog, 79 logger: logger, 80 stack: stack.push(), 81 scope: scope, 82 transactions: $transactions, 83 reloadAllAction: reloadAllAction) 84 .padding(.leading, ICONLEADING) 85 } header: { 86 let header = scope.url?.trimURL ?? scope.currency 87 Text(header) 88 .talerFont(.title3) 89 .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast)) 90 } 91 } 92 .id(viewId) 93 .listStyle(myListStyle.style).anyView 94 .refreshable { 95 controller.hapticNotification(.success) 96 symLog.log("refreshing") 97 await reloadAllAction(stack.push()) 98 } 99 #if false // SCROLLBUTTONS 100 .if(count > showUpDown) { view in 101 view.navigationBarItems(trailing: HStack { 102 ArrowUpButton { 103 // print("up") 104 withAnimation { scrollProxy.scrollTo(0) } 105 } 106 ArrowDownButton { 107 // print("down") 108 withAnimation { scrollProxy.scrollTo(transactions.count - 1) } 109 } 110 }) 111 } 112 #endif 113 } // ScrollViewReader 114 // .navigationTitle("EURO") // Fake EUR instead of the real Currency 115 // .navigationTitle("CHF") // Fake CHF instead of the real Currency 116 .navigationTitle(navTitle ?? scope.currency) 117 .accessibilityHint(String(localized: "Transaction list", comment: "a11y")) 118 .task { 119 symLog.log("❗️.task List❗️") 120 await reloadAllAction(stack.push()) 121 } 122 .onAppear { 123 DebugViewC.shared.setViewID(VIEW_TRANSACTIONLIST, stack: stack.push()) 124 print("🚩,32TransactionsListView.onAppear() set selectedBalance to", balance.scopeInfo.currency) 125 selectedBalance = balance // set this balance (fix) for send/request/deposit/withdraw 126 } 127 // } 128 #if OIM 129 .overlay { if #available(iOS 16.4, *) { 130 if controller.oimModeActive { 131 OIMtransactions(stack: stack.push(), 132 balance: balance, 133 cash: cash, 134 history: transactions) 135 .environmentObject(NamespaceWrapper(namespace)) // keep OIMviews apart 136 } 137 } } 138 #endif 139 } // not empty 140 } // body 141 } 142 // MARK: - 143 // used by TransactionsListView, and by BalancesSectionView to show the last 3 transactions 144 struct TransactionsArraySliceV: View { 145 let symLog: SymLogV? 146 let logger: Logger? 147 let stack: CallStack 148 let scope: ScopeInfo 149 @Binding var transactions: [TalerTransaction] 150 let reloadAllAction: (_ stack: CallStack) async -> () 151 152 @EnvironmentObject private var model: WalletModel 153 @State private var talerTX: TalerTransaction = TalerTransaction(dummyCurrency: DEMOCURRENCY) 154 155 var body: some View { 156 #if PRINT_CHANGES 157 let _ = Self._printChanges() 158 let _ = symLog?.vlog() // just to get the # to compare it with .onAppear & onDisappear 159 #endif 160 let abortAction = model.abortTransaction 161 let deleteAction = model.deleteTransaction 162 let failAction = model.failTransaction 163 let suspendAction = model.suspendTransaction 164 let resumeAction = model.resumeTransaction 165 166 ForEach(transactions, id: \.self) { transaction in 167 let destination = TransactionSummaryV(stack: stack.push(), 168 // scope: scope, 169 transactionId: transaction.id, 170 talerTX: $talerTX, 171 navTitle: nil, 172 hasDone: false, 173 abortAction: abortAction, 174 deleteAction: deleteAction, 175 failAction: failAction, 176 suspendAction: suspendAction, 177 resumeAction: resumeAction) 178 let row = NavigationLink { destination } label: { 179 TransactionRowView(logger: logger, scope: scope, transaction: transaction) 180 }.id(transaction.id) 181 if transaction.isDeleteable { 182 row.swipeActions(edge: .trailing) { 183 Button { 184 symLog?.log("deleteAction") 185 Task { // runs on MainActor 186 let _ = try? await deleteAction(transaction.id, false) 187 await reloadAllAction(stack.push()) 188 } 189 } label: { 190 Label("Delete", systemImage: "trash") 191 } 192 .tint(WalletColors().negative) 193 } 194 } else { 195 row 196 } 197 } 198 } 199 }