taler-ios

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

BalancesSectionView.swift (9713B)


      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 taler_swift
     11 import SymLog
     12 
     13 /// This view shows a currency section
     14 ///     Currency Name
     15 /// "Balance"                   $balance                    // leads to completed Transactions (.done)
     16 /// optional:   Pending Incoming
     17 /// optional:   Pending Outgoing
     18 /// optional?:   Suspended / Aborting / Aborted / Expired
     19 struct BalancesSectionView {
     20     private let symLog = SymLogV(0)
     21     let stack: CallStack
     22     let balance: Balance                            // this is the currency to be used
     23     @Binding var selectedBalance: Balance?          // <- return here the balance when we go to Transactions
     24     let balanceIndex: Int
     25     let sectionCount: Int
     26     @Binding var amountToTransfer: Amount           // does still have the wrong currency
     27     @Binding var summary: String
     28     @Binding var historyTapped: Int?
     29     @Binding var reloadTransactions: Int
     30 
     31     let logger = Logger(subsystem: "net.taler.gnu", category: "RecentList")
     32     @EnvironmentObject private var model: WalletModel
     33     @Environment(\.colorScheme) private var colorScheme
     34     @Environment(\.colorSchemeContrast) private var colorSchemeContrast
     35     @EnvironmentObject private var controller: Controller
     36 #if DEBUG
     37     @AppStorage("developerMode") var developerMode: Bool = true
     38 #else
     39     @AppStorage("developerMode") var developerMode: Bool = false
     40 #endif
     41     @AppStorage("minimalistic") var minimalistic: Bool = false
     42     @AppStorage("oimEuro") var oimEuro: Bool = false
     43 
     44     @State private var showSpendingHint = true
     45     @State private var isShowingDetailView = false
     46     @State private var completedTransactions: [TalerTransaction] = []
     47     @State private var recentTransactions: [TalerTransaction] = []
     48     @State private var pendingTransactions: [TalerTransaction] = []
     49     @State private var scannedTransactions: [TalerTransaction] = []
     50 
     51     private static func className() -> String {"\(self)"}
     52 
     53     @State private var sectionID = UUID()
     54     @State private var shownSectionID = UUID()  // guaranteed to be different the first time
     55 
     56     @MainActor
     57     func loadRecent(_ stack: CallStack) async -> () {
     58         if let transactions = try? await model.getTransactionsV2(stack.push("loadRecent - \(balance.scopeInfo.url?.trimURL)"),
     59                                                           scope: balance.scopeInfo,
     60                                                   filterByState: .final,
     61                                                           limit: MAXRECENT,
     62                                                includeRefreshes: false) {
     63             withAnimation { recentTransactions = transactions }
     64         }
     65     }
     66     @MainActor
     67     func loadCompleted(_ stack: CallStack) async -> () {
     68         if let transactions = try? await model.getTransactionsV2(stack.push("loadCompleted - \(balance.scopeInfo.url?.trimURL)"),
     69                                                           scope: balance.scopeInfo,
     70                                                   filterByState: .final,
     71                                                includeRefreshes: developerMode) {
     72             withAnimation { completedTransactions = transactions }
     73         }
     74     }
     75     @MainActor
     76     func loadPending(_ stack: CallStack) async -> () {
     77         if let transactions = try? await model.getTransactionsV2(stack.push("loadPending - \(balance.scopeInfo.url?.trimURL)"),
     78                                                           scope: balance.scopeInfo,
     79                                                   filterByState: .nonfinalApproved,
     80                                                includeRefreshes: developerMode) {
     81             withAnimation { pendingTransactions = transactions }
     82         }
     83     }
     84     @MainActor
     85     func loadScanned(_ stack: CallStack) async -> () {
     86         if let transactions = try? await model.getTransactionsV2(stack.push("loadScanned - \(balance.scopeInfo.url?.trimURL)"),
     87                                                           scope: balance.scopeInfo,
     88                                                   filterByState: .nonfinalDialog,
     89                                                includeRefreshes: developerMode) {
     90             withAnimation { scannedTransactions = transactions }
     91         }
     92     }
     93 }
     94 
     95 extension BalancesSectionView: View {
     96     var body: some View {
     97 #if PRINT_CHANGES
     98         let _ = Self._printChanges()
     99         let _ = symLog.vlog()       // just to get the # to compare it with .onAppear & onDisappear
    100 #endif
    101         let scopeInfo = balance.scopeInfo
    102         let balanceDest = TransactionsListView(stack: stack.push("BalanceCellV"),
    103                                                scope: scopeInfo,
    104                                              balance: balance,
    105                                      selectedBalance: $selectedBalance,
    106                                             navTitle: balance.available.readableDescription,    // TODO: format with currency sign
    107                                              oimEuro: oimEuro,
    108                                         transactions: $completedTransactions,
    109                                      reloadAllAction: loadCompleted)
    110         let isLegacy = scopeInfo.type == .exchangeLegacyKeys
    111         Section {
    112             if scopeInfo.type == .exchange || isLegacy {
    113                 let baseURL = scopeInfo.url?.trimURL ?? String(localized: "Unknown payment service", comment: "exchange url")
    114                 Text(baseURL)
    115                     .talerFont(.subheadline)
    116                     .foregroundColor(.secondary)
    117             //      .listRowSeparator(.hidden)
    118             }
    119             BalanceCellV(stack: stack.push("BalanceCell"),
    120                          scope: balance.scopeInfo,
    121                         amount: balance.available,
    122                       isLegacy: isLegacy,
    123                  historyTapped: $historyTapped,
    124                   balanceIndex: balanceIndex,
    125                    balanceDest: balanceDest)
    126 //            .listRowSeparator(.hidden)
    127 //            .border(.red)
    128 
    129             if !pendingTransactions.isEmpty {
    130                 BalancesPendingRowV(//symLog: symLog,
    131                                      stack: stack.push(),
    132                                    balance: balance,
    133                            selectedBalance: $selectedBalance,
    134                        pendingTransactions: $pendingTransactions,
    135                              reloadPending: loadPending)
    136                     .padding(.leading, ICONLEADING)
    137             }
    138             if !scannedTransactions.isEmpty {
    139                 BalancesDialogRowV(stack: stack.push(),
    140                                  balance: balance,
    141                          selectedBalance: $selectedBalance,
    142                      scannedTransactions: $scannedTransactions,
    143                            reloadScanned: loadScanned)
    144                   .padding(.leading, ICONLEADING)
    145               }
    146         } header: {
    147             BarGraphHeader(stack: stack.push(),
    148                            scope: scopeInfo,
    149               reloadTransactions: $reloadTransactions)
    150         }.id(sectionID)
    151         .listRowSeparator(.hidden)
    152         .task(id: reloadTransactions + 1_000_000) {
    153             symLog.log(".task for BalancesSectionView - load recent+completed+pending")
    154             await loadRecent(stack.push(".task - load recent"))
    155             await loadCompleted(stack.push(".task - load completed"))       // TODO: only in TX list view
    156             await loadPending(stack.push(".task - load pending"))
    157             await loadScanned(stack.push(".task - load scanned"))
    158         }
    159         if sectionCount == 1 {
    160             // show Discounts & Passes before recents
    161             DiscountPassesSection(stack: stack.push())
    162             // if there is only one currency, then show MAXRECENT recent transactions
    163             let recentCount = recentTransactions.count
    164             if recentCount > 0 {
    165                 let _ = symLog.log("recent transactions")
    166                 let recentHeader = minimalistic ? nil
    167                                  : recentCount > 1
    168                     ? String(localized: "Recent transactions", comment: "section header plural")
    169                     : String(localized: "Recent transaction", comment: "section header singular")
    170                 TransactionsArraySection(symLog: symLog,
    171                                          logger: logger,
    172                                           stack: stack.push(),
    173                                          header: recentHeader,
    174                                           scope: scopeInfo,
    175                                    transactions: $recentTransactions,
    176                                 reloadAllAction: loadRecent)
    177             } // recent transactions
    178         } // 1 Section only
    179     } // body
    180 } // BalancesSectionView
    181 // MARK: -
    182 #if false   // model crashes
    183 struct BalancesSectionView_Previews: PreviewProvider {
    184 fileprivate struct BindingViewContainer: View {
    185     @State var amountToTransfer: UInt64 = 333
    186     @State private var summary: String = "bla-bla"
    187 
    188     var body: some View {
    189         let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, url: DEMOEXCHANGE, currency: LONGCURRENCY)
    190         let balance = Balance(scopeInfo: scopeInfo,
    191                               available: Amount(currency: LONGCURRENCY, cent:1),
    192                               hasPendingTransactions: true)
    193         BalancesSectionView(balance: balance,
    194                        sectionCount: 2,
    195                    amountToTransfer: $amountToTransfer,
    196                             summary: $summary)
    197     }
    198 }
    199 
    200     static var previews: some View {
    201         List {
    202             BindingViewContainer()
    203         }
    204     }
    205 }
    206 #endif