taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

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 }