taler-ios

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

TransactionsListView.swift (9950B)


      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     @AppStorage("preferredColorScheme") var preferredColorScheme: Int = 0
     35     @State private var viewId = UUID()
     36     @StateObject private var cash: OIMcash
     37     @Namespace var namespace
     38 
     39     init(stack: CallStack,
     40          scope: ScopeInfo,
     41          balance: Balance,
     42          selectedBalance: Binding<Balance?>,
     43          navTitle: String?,
     44          oimEuro: Bool,
     45          transactions: Binding<[TalerTransaction]>,
     46          reloadAllAction: @escaping (_ stack: CallStack) async -> ()
     47     ) {
     48         // SwiftUI ensures that the initialization uses the
     49         // closure only once during the lifetime of the view, so
     50         // later changes to the currency have no effect.
     51         self.stack = stack
     52         self.scope = scope
     53         self.balance = balance
     54         self.navTitle = navTitle
     55         self._transactions = transactions
     56         self.reloadAllAction = reloadAllAction
     57         self._selectedBalance = selectedBalance
     58         let oimCurrency = oimCurrency(balance.scopeInfo, oimEuro: oimEuro)
     59         let oimCash = OIMcash(oimCurrency)
     60         self._cash = StateObject(wrappedValue: { oimCash }())
     61     }
     62     var body: some View {
     63 #if PRINT_CHANGES
     64         let _ = Self._printChanges()
     65         let _ = symLog.vlog()       // just to get the # to compare it with .onAppear & onDisappear
     66 #endif
     67         if transactions.isEmpty {
     68             TransactionsEmptyView(stack: stack.push(), currency: scope.currency)
     69                 .refreshable {
     70                     controller.hapticNotification(.success)
     71                     symLog.log("refreshing")
     72                     await reloadAllAction(stack.push())
     73                 }
     74         } else {
     75             let list = 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                     .background(FullBackground())
     89                     .refreshable {
     90                         controller.hapticNotification(.success)
     91                         symLog.log("refreshing")
     92                         await reloadAllAction(stack.push())
     93                     }
     94 #if false // SCROLLBUTTONS
     95                     .if(count > showUpDown) { view in
     96                         view.navigationBarItems(trailing: HStack {
     97                             ArrowUpButton {
     98 //                                print("up")
     99                                 withAnimation { scrollProxy.scrollTo(0) }
    100                             }
    101                             ArrowDownButton {
    102 //                                print("down")
    103                                 withAnimation { scrollProxy.scrollTo(transactions.count - 1) }
    104                             }
    105                         })
    106                     }
    107 #endif
    108                 } // ScrollViewReader
    109 //              .navigationTitle("EURO")           // Fake EUR instead of the real Currency
    110 //              .navigationTitle("CHF")            // Fake CHF instead of the real Currency
    111                 .navigationTitle(navTitle ?? scope.currency)
    112                 .accessibilityHint(String(localized: "Transaction list", comment: "a11y"))
    113                 .task {
    114                     symLog.log("❗️.task List❗️")
    115                     await reloadAllAction(stack.push())
    116                 }
    117                 .onAppear {
    118                     DebugViewC.shared.setViewID(VIEW_TRANSACTIONLIST, stack: stack.push())
    119                     print("🚩,32TransactionsListView.onAppear() set selectedBalance to", balance.scopeInfo.currency)
    120                     selectedBalance = balance           // set this balance (fix) for send/request/deposit/withdraw
    121                 }
    122 
    123             ZStack {
    124                 if preferredColorScheme == 3, #available(iOS 17.0, *) {
    125                     list.scrollContentBackground(.hidden)
    126                 } else {
    127                     list
    128                 }
    129             }
    130 #if OIM
    131             .overlay { if #available(iOS 16.4, *) {
    132                 if controller.oimModeActive {
    133                     OIMtransactions(stack: stack.push(),
    134                                   balance: balance,
    135                                      cash: cash,
    136                                   history: transactions)
    137                     .environmentObject(NamespaceWrapper(namespace))         // keep OIMviews apart
    138                 }
    139             } }
    140 #endif
    141         } // not empty
    142     } // body
    143 }
    144 // MARK: -
    145 // used by TransactionsListView, and by BalancesSectionView to show the last 4 transactions
    146 struct TransactionsArraySection: View {
    147     let symLog: SymLogV?
    148     let logger: Logger?
    149     let stack: CallStack
    150     let header: String?
    151     let scope: ScopeInfo
    152     @Binding var transactions: [TalerTransaction]
    153     let reloadAllAction: (_ stack: CallStack) async -> ()
    154 
    155     @EnvironmentObject private var model: WalletModel
    156     @Environment(\.colorScheme) private var colorScheme
    157     @Environment(\.colorSchemeContrast) private var colorSchemeContrast
    158 #if DEBUG
    159     @AppStorage("developerMode") var developerMode: Bool = true
    160 #else
    161     @AppStorage("developerMode") var developerMode: Bool = false
    162 #endif
    163     @AppStorage("debugViews") var debugViews: Bool = false
    164 
    165     @State private var talerTX: TalerTransaction = TalerTransaction(dummyCurrency: DEMOCURRENCY)
    166 
    167     @State private var padd = 0
    168 
    169     @ViewBuilder
    170     func headerView(_ header: String) -> some View {
    171         Text(header)
    172             .talerFont(.title3)
    173             .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast))
    174     }
    175 
    176     var body: some View {
    177 #if PRINT_CHANGES
    178         let _ = Self._printChanges()
    179         let _ = symLog?.vlog()       // just to get the # to compare it with .onAppear & onDisappear
    180 #endif
    181         let deleteAction = model.deleteTransaction
    182 
    183         Section {
    184             ForEach(transactions, id: \.self) { transaction in
    185                 let destination = TransactionSummaryList(stack: stack.push("TransactionsArraySection"),
    186                                                  transactionId: transaction.id,
    187                                                        talerTX: $talerTX,
    188                                                       navTitle: nil,
    189                                                        hasDone: false,
    190                                                       showDone: nil,
    191                                                            url: nil,
    192                                                    withActions: true)
    193                 let row = NavigationLink { destination } label: {
    194                     TransactionRowView(logger: logger, scope: scope, transaction: transaction)
    195                         .padding(.leading, ICONLEADING)
    196                         .padding(.trailing, CGFloat(padd))
    197                 }.id(transaction.id)
    198                 if transaction.isDeleteable {
    199                     row.swipeActions(edge: .trailing) {
    200                             Button {
    201                                 symLog?.log("deleteAction")
    202                                 Task { // runs on MainActor
    203                                     let _ = try? await deleteAction(transaction.id, false)
    204                                     await reloadAllAction(stack.push())
    205                                 }
    206                             } label: {
    207                                 Label("Delete", systemImage: "trash")
    208                             }
    209                             .tint(WalletColors().negative)
    210                         }
    211                 } else {
    212                     row
    213                 }
    214             }
    215         } header: {
    216 #if TALER_NIGHTLY
    217             if developerMode && debugViews {
    218                 HStack {
    219                     Button("<<") { padd += 10 }
    220                     Spacer()
    221                     Button("<") { padd += 1 }
    222                     Spacer()
    223                     Button("\(padd)") { padd = 0 }
    224                     Spacer()
    225                     Button(">") { padd -= 1 }
    226                     Spacer()
    227                     Button(">>") { padd -= 10 }
    228                 }.font(.body)
    229             } else {
    230                 if let header {
    231                     headerView(header)
    232                 }
    233             }
    234 #else
    235             if let header {
    236                 headerView(header)
    237             }
    238 #endif
    239         }
    240     }
    241 }