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