taler-ios

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

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 }