taler-ios

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

BalancesSectionView.swift (9398B)


      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("\(Self.className())()"),
    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         Section {
    111             if scopeInfo.type == .exchange {
    112                 let baseURL = scopeInfo.url?.trimURL ?? String(localized: "Unknown payment service", comment: "exchange url")
    113                 Text(baseURL)
    114                     .talerFont(.subheadline)
    115                     .foregroundColor(.secondary)
    116             //      .listRowSeparator(.hidden)
    117             }
    118             BalanceCellV(stack: stack.push("BalanceCell"),
    119                          scope: balance.scopeInfo,
    120                         amount: balance.available,
    121                  historyTapped: $historyTapped,
    122                   balanceIndex: balanceIndex,
    123                    balanceDest: balanceDest)
    124 //            .listRowSeparator(.hidden)
    125 //            .border(.red)
    126 
    127             if !pendingTransactions.isEmpty {
    128                 BalancesPendingRowV(//symLog: symLog,
    129                                      stack: stack.push(),
    130                                    balance: balance,
    131                            selectedBalance: $selectedBalance,
    132                        pendingTransactions: $pendingTransactions,
    133                              reloadPending: loadPending)
    134                     .padding(.leading, ICONLEADING)
    135             }
    136             if !scannedTransactions.isEmpty {
    137                 BalancesDialogRowV(stack: stack.push(),
    138                                  balance: balance,
    139                          selectedBalance: $selectedBalance,
    140                      scannedTransactions: $scannedTransactions,
    141                            reloadScanned: loadScanned)
    142                   .padding(.leading, ICONLEADING)
    143               }
    144         } header: {
    145             BarGraphHeader(stack: stack.push(),
    146                            scope: scopeInfo,
    147               reloadTransactions: $reloadTransactions)
    148         }.id(sectionID)
    149         .listRowSeparator(.hidden)
    150         .task(id: reloadTransactions + 1_000_000) {
    151             symLog.log(".task for BalancesSectionView - load recent+completed+pending")
    152             await loadRecent(stack.push(".task - load recent"))
    153             await loadCompleted(stack.push(".task - load completed"))       // TODO: only in TX list view
    154             await loadPending(stack.push(".task - load pending"))
    155             await loadScanned(stack.push(".task - load scanned"))
    156         }
    157 
    158         /// if there is only one currency, then show MAXRECENT recent transactions
    159         let recentCount = recentTransactions.count
    160         if sectionCount == 1 && recentCount > 0 {
    161             let _ = symLog.log("recent transactions")
    162             let recentHeader = minimalistic ? nil
    163                              : recentCount > 1
    164                 ? String(localized: "Recent transactions", comment: "section header plural")
    165                 : String(localized: "Recent transaction", comment: "section header singular")
    166             TransactionsArraySection(symLog: symLog,
    167                                      logger: logger,
    168                                       stack: stack.push(),
    169                                      header: recentHeader,
    170                                       scope: scopeInfo,
    171                                transactions: $recentTransactions,
    172                             reloadAllAction: loadRecent)
    173         } // recent transactions
    174     } // body
    175 } // BalancesSectionView
    176 // MARK: -
    177 #if false   // model crashes
    178 struct BalancesSectionView_Previews: PreviewProvider {
    179 fileprivate struct BindingViewContainer: View {
    180     @State var amountToTransfer: UInt64 = 333
    181     @State private var summary: String = "bla-bla"
    182 
    183     var body: some View {
    184         let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, url: DEMOEXCHANGE, currency: LONGCURRENCY)
    185         let balance = Balance(scopeInfo: scopeInfo,
    186                               available: Amount(currency: LONGCURRENCY, cent:1),
    187                               hasPendingTransactions: true)
    188         BalancesSectionView(balance: balance,
    189                        sectionCount: 2,
    190                    amountToTransfer: $amountToTransfer,
    191                             summary: $summary)
    192     }
    193 }
    194 
    195     static var previews: some View {
    196         List {
    197             BindingViewContainer()
    198         }
    199     }
    200 }
    201 #endif