taler-ios

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

BalancesSectionView.swift (10006B)


      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 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 
    103         Group {
    104             let balanceDest = TransactionsListView(stack: stack.push("\(Self.className())()"),
    105                                                    scope: scopeInfo,
    106                                                  balance: balance,
    107                                          selectedBalance: $selectedBalance,
    108                                                 navTitle: balance.available.readableDescription,    // TODO: format with currency sign
    109                                                  oimEuro: oimEuro,
    110                                             transactions: $completedTransactions,
    111                                          reloadAllAction: loadCompleted)
    112             Section {
    113                 if scopeInfo.type == .exchange {
    114                     let baseURL = scopeInfo.url?.trimURL ?? String(localized: "Unknown payment service", comment: "exchange url")
    115                     Text(baseURL)
    116                         .talerFont(.subheadline)
    117                         .foregroundColor(.secondary)
    118                 //      .listRowSeparator(.hidden)
    119                 }
    120                 BalanceCellV(stack: stack.push("BalanceCell"),
    121                              scope: balance.scopeInfo,
    122                             amount: balance.available,
    123                      historyTapped: $historyTapped,
    124                       balanceIndex: balanceIndex,
    125                        balanceDest: balanceDest)
    126 //                .listRowSeparator(.hidden)
    127 //                .border(.red)
    128 
    129                 if pendingTransactions.count > 0 {
    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.count > 0 {
    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 there is only one currency, then show MAXRECENT recent transactions
    160             if sectionCount == 1 && recentTransactions.count > 0 {
    161                 Section {
    162                     let _ = symLog.log("recent transactions")
    163                     TransactionsArraySliceV(symLog: symLog,
    164                                             logger: logger,
    165                                              stack: stack.push(),
    166                                              scope: scopeInfo,
    167                                             transactions: $recentTransactions,
    168                                             reloadAllAction: loadRecent)
    169                         .padding(.leading, ICONLEADING)
    170                 } header: {
    171                     if !minimalistic {
    172                         let recentHeader = recentTransactions.count > 1
    173                             ? String(localized: "Recent transactions", comment: "section header plural")
    174                             : String(localized: "Recent transaction", comment: "section header singular")
    175                         Text(recentHeader)
    176                             .talerFont(.title3)
    177                             .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast))
    178                     }
    179                 }
    180             } // recent transactions
    181         }
    182     } // body
    183 } // BalancesSectionView
    184 // MARK: -
    185 #if false   // model crashes
    186 struct BalancesSectionView_Previews: PreviewProvider {
    187 fileprivate struct BindingViewContainer: View {
    188     @State var amountToTransfer: UInt64 = 333
    189     @State private var summary: String = "bla-bla"
    190 
    191     var body: some View {
    192         let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, url: DEMOEXCHANGE, currency: LONGCURRENCY)
    193         let balance = Balance(scopeInfo: scopeInfo,
    194                               available: Amount(currency: LONGCURRENCY, cent:1),
    195                               hasPendingTransactions: true)
    196         BalancesSectionView(balance: balance,
    197                        sectionCount: 2,
    198                    amountToTransfer: $amountToTransfer,
    199                             summary: $summary)
    200     }
    201 }
    202 
    203     static var previews: some View {
    204         List {
    205             BindingViewContainer()
    206         }
    207     }
    208 }
    209 #endif