taler-ios

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

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 }