taler-ios

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

commit e11066ff16fa260ced62a3a2d0e9107dbd831c82
parent 32eb6cd428efabea97739e7ec75631a84e1ced22
Author: Marc Stibane <marc@taler.net>
Date:   Sat, 28 Sep 2024 09:03:02 +0200

delete rows (swipeActions)

Diffstat:
MTalerWallet1/Views/Balances/BalancesPendingRowV.swift | 6+++---
MTalerWallet1/Views/Balances/BalancesSectionView.swift | 63+++++++++++++++++++++++++++++++++------------------------------
MTalerWallet1/Views/Overview/OverviewRowV.swift | 7++++++-
MTalerWallet1/Views/Overview/OverviewSectionV.swift | 72+++++++++++++++++++++++++++++++++++++++++-------------------------------
MTalerWallet1/Views/Transactions/TransactionsListView.swift | 59++++++++++++++++++++++++++++++++++++++---------------------
5 files changed, 121 insertions(+), 86 deletions(-)

diff --git a/TalerWallet1/Views/Balances/BalancesPendingRowV.swift b/TalerWallet1/Views/Balances/BalancesPendingRowV.swift @@ -64,7 +64,7 @@ struct BalancesPendingRowV: View { currencyInfo: $currencyInfo, navTitle: String(localized: "Pending", comment: "ViewTitle of TransactionList"), scopeInfo: balance.scopeInfo, - transactions: pendingTransactions, + transactions: $pendingTransactions, reloadAllAction: reloadPending, reloadOneAction: reloadOneAction) } @@ -81,7 +81,7 @@ struct BalancesPendingRowV: View { fileprivate struct BalancesPendingRowV_Previews: PreviewProvider { @MainActor struct BindingViewContainer: View { - @State private var pendingTransactions: [Transaction] = [] + @State private var previewTransactions: [Transaction] = [] @State private var currencyInfoD: CurrencyInfo = CurrencyInfo.zero(DEMOCURRENCY) var body: some View { let flags: [BalanceFlag] = [.incomingConfirmation] @@ -96,7 +96,7 @@ fileprivate struct BalancesPendingRowV_Previews: PreviewProvider { stack: CallStack("Preview"), currencyInfo: $currencyInfoD, balance: balance, - pendingTransactions: $pendingTransactions, + pendingTransactions: $previewTransactions, reloadPending: {stack in }, reloadOneAction: nil) } diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift b/TalerWallet1/Views/Balances/BalancesSectionView.swift @@ -42,8 +42,8 @@ struct BalancesSectionView { @State private var showSpendingHint = true @State private var isShowingDetailView = false - @State private var transactions: [Transaction] = [] @State private var completedTransactions: [Transaction] = [] + @State private var recentTransactions: [Transaction] = [] @State private var pendingTransactions: [Transaction] = [] @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN) @State private var currencyName: String = UNKNOWN @@ -59,21 +59,32 @@ struct BalancesSectionView { @State private var sectionID = UUID() @State private var shownSectionID = UUID() // guaranteed to be different the first time - func reloadCompleted(_ stack: CallStack) async -> () { + func loadRecent(_ stack: CallStack) async -> () { if let transactions = try? await model.transactionsT(stack.push(), scopeInfo: balance.scopeInfo, -// filterByState: .final, // TODO: .final +// filterByState: .done, // TODO: .done includeRefreshes: false) { - completedTransactions = WalletModel.completedTransactions(transactions) + let recent = WalletModel.completedTransactions(transactions) + // TODO: only load the MAXRECENT most recent transactions + let slice = recent.prefix(MAXRECENT) // already sorted + withAnimation { recentTransactions = Array(slice) } } } - - func reloadPending(_ stack: CallStack) async -> () { + func loadCompleted(_ stack: CallStack) async -> () { + if let transactions = try? await model.transactionsT(stack.push(), + scopeInfo: balance.scopeInfo, +// filterByState: .final, // TODO: .final + includeRefreshes: developerMode) { + let completed = WalletModel.completedTransactions(transactions) + withAnimation { completedTransactions = completed } + } + } + func loadPending(_ stack: CallStack) async -> () { if let transactions = try? await model.transactionsT(stack.push(), scopeInfo: balance.scopeInfo, filterByState: .nonfinal, includeRefreshes: developerMode) { - pendingTransactions = WalletModel.pendingTransactions(transactions) + withAnimation { pendingTransactions = WalletModel.pendingTransactions(transactions) } } } } @@ -93,8 +104,8 @@ extension BalancesSectionView: View { currencyInfo: $currencyInfo, navTitle: String(localized: "Transactions", comment: "ViewTitle of TransactionList"), scopeInfo: scopeInfo, - transactions: completedTransactions, - reloadAllAction: reloadCompleted, + transactions: $completedTransactions, + reloadAllAction: loadCompleted, reloadOneAction: reloadOneAction) } @@ -139,7 +150,7 @@ extension BalancesSectionView: View { currencyInfo: $currencyInfo, balance: balance, pendingTransactions: $pendingTransactions, - reloadPending: reloadPending, + reloadPending: loadPending, reloadOneAction: reloadOneAction) .padding(.leading, ICONLEADING) } @@ -188,37 +199,29 @@ extension BalancesSectionView: View { currencySymbol = currencyInfo.altUnitSymbol ?? currencyName } .task(id: shouldReloadBalances + 1_000_000) { -// if shownSectionID != sectionID { - symLog.log(".task for BalancesSectionView - reload Transactions") - // TODO: only load the MAXRECENT most recent transactions - if let response = try? await model.transactionsT(stack.push(".task - reload Transactions"), scopeInfo: scopeInfo, includeRefreshes: developerMode) { - transactions = response - pendingTransactions = WalletModel.pendingTransactions(response) - completedTransactions = WalletModel.completedTransactions(response) - shownSectionID = sectionID - } -// } else { -// symLog.log("task for BalancesSectionView \(sectionID) ❗️ skip reloading Transactions") -// } + symLog.log(".task for BalancesSectionView - load recent+completed+pending") + await loadRecent(stack.push(".task - load recent")) + await loadCompleted(stack.push(".task - load completed")) + await loadPending(stack.push(".task - load pending")) } - let transactionCount = completedTransactions.count /// if there is only one currency, then show MAXRECENT recent transactions - if sectionCount == 1 && transactionCount > 0 { + if sectionCount == 1 && recentTransactions.count > 0 { Section { - let slice = completedTransactions.prefix(MAXRECENT) // already sorted - let threeTransactions = Array(slice) + let _ = symLog.log("recent transactions") TransactionsArraySliceV(symLog: symLog, stack: stack.push(), currencyInfo: $currencyInfo, scopeInfo: scopeInfo, - transactions: threeTransactions, + transactions: $recentTransactions, + reloadAllAction: loadRecent, reloadOneAction: reloadOneAction) .padding(.leading, ICONLEADING) } header: { if !minimalistic { - let count = transactionCount > MAXRECENT ? MAXRECENT : transactionCount - Text(count > 1 ? "Recent \(count) transactions" - : "Recent transaction") + let recentHeader = recentTransactions.count > 1 + ? String(localized: "Recent transactions", comment: "section header plural") + : String(localized: "Recent transaction", comment: "section header singular") + Text(recentHeader) .talerFont(.title3) .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast)) } diff --git a/TalerWallet1/Views/Overview/OverviewRowV.swift b/TalerWallet1/Views/Overview/OverviewRowV.swift @@ -23,7 +23,12 @@ struct CurrenciesCell: View { /// Renders the Balance button. "Balance" leading, amountStr trailing. If it doesn't fit in one row then /// amount (trailing) goes underneath "Balance" (leading). var body: some View { - let amountV = AmountV(stack: stack.push(), currencyInfo: $currencyInfo, amount: amount, isNegative: false, large: true) + let amountV = AmountV(stack: stack.push(), + currencyInfo: $currencyInfo, + amount: amount, + isNegative: nil, // don't show the + sign + strikethrough: false, + large: true) .foregroundColor(.primary) let hLayout = amountV .frame(maxWidth: .infinity, alignment: .trailing) diff --git a/TalerWallet1/Views/Overview/OverviewSectionV.swift b/TalerWallet1/Views/Overview/OverviewSectionV.swift @@ -36,8 +36,8 @@ struct OverviewSectionV { @State private var showSpendingHint = true @State private var isShowingDetailView = false - @State private var transactions: [Transaction] = [] @State private var completedTransactions: [Transaction] = [] + @State private var recentTransactions: [Transaction] = [] @State private var pendingTransactions: [Transaction] = [] @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN) @State private var currencyName: String = UNKNOWN @@ -50,15 +50,32 @@ struct OverviewSectionV { @State private var sectionID = UUID() @State private var shownSectionID = UUID() // guaranteed to be different the first time - func reloadCompleted(_ stack: CallStack) async -> () { - if let transactions = try? await model.transactionsT(stack.push(), scopeInfo: balance.scopeInfo, includeRefreshes: developerMode) { - completedTransactions = WalletModel.completedTransactions(transactions) + func loadRecent(_ stack: CallStack) async -> () { + if let transactions = try? await model.transactionsT(stack.push(), + scopeInfo: balance.scopeInfo, +// filterByState: .done, // TODO: .done + includeRefreshes: false) { + let recent = WalletModel.completedTransactions(transactions) + // TODO: only load the MAXRECENT most recent transactions + let slice = recent.prefix(MAXRECENT) // already sorted + withAnimation { recentTransactions = Array(slice) } } } - - func reloadPending(_ stack: CallStack) async -> () { - if let transactions = try? await model.transactionsT(stack.push(), scopeInfo: balance.scopeInfo, includeRefreshes: developerMode) { - pendingTransactions = WalletModel.pendingTransactions(transactions) + func loadCompleted(_ stack: CallStack) async -> () { + if let transactions = try? await model.transactionsT(stack.push(), + scopeInfo: balance.scopeInfo, +// filterByState: .final, // TODO: .final + includeRefreshes: developerMode) { + let completed = WalletModel.completedTransactions(transactions) + withAnimation { completedTransactions = completed } + } + } + func loadPending(_ stack: CallStack) async -> () { + if let transactions = try? await model.transactionsT(stack.push(), + scopeInfo: balance.scopeInfo, + filterByState: .nonfinal, + includeRefreshes: developerMode) { + withAnimation { pendingTransactions = WalletModel.pendingTransactions(transactions) } } } } @@ -88,7 +105,7 @@ extension OverviewSectionV: View { amountToTransfer: $amountToTransfer, // does still have the wrong currency summary: $summary, completedTransactions: $completedTransactions, - reloadAllAction: reloadCompleted, + reloadAllAction: loadCompleted, reloadOneAction: reloadOneAction) if pendingTransactions.count > 0 { BalancesPendingRowV(//symLog: symLog, @@ -96,7 +113,7 @@ extension OverviewSectionV: View { currencyInfo: $currencyInfo, balance: balance, pendingTransactions: $pendingTransactions, - reloadPending: reloadPending, + reloadPending: loadPending, reloadOneAction: reloadOneAction) .padding(.leading, ICONLEADING) } @@ -113,37 +130,30 @@ extension OverviewSectionV: View { currencySymbol = currencyInfo.altUnitSymbol ?? currencyName } .task(id: shouldReloadBalances + 1_000_000) { -// if shownSectionID != sectionID { - symLog.log(".task for BalancesSectionView - reload Transactions") - // TODO: only load the MAXRECENT most recent transactions - if let response = try? await model.transactionsT(stack.push(".task - reload Transactions"), scopeInfo: scopeInfo, includeRefreshes: developerMode) { - transactions = response - pendingTransactions = WalletModel.pendingTransactions(response) - completedTransactions = WalletModel.completedTransactions(response) - shownSectionID = sectionID - } -// } else { -// symLog.log("task for BalancesSectionView \(sectionID) ❗️ skip reloading Transactions") -// } + symLog.log(".task for OverviewSectionV - load recent+completed+pending") + await loadRecent(stack.push(".task - load recent")) + await loadCompleted(stack.push(".task - load completed")) + await loadPending(stack.push(".task - load pending")) + shownSectionID = sectionID } - let transactionCount = completedTransactions.count /// if there is only one currency, then show MAXRECENT recent transactions - if sectionCount == 1 && transactionCount > 0 { + if sectionCount == 1 && recentTransactions.count > 0 { Section { - let slice = completedTransactions.prefix(MAXRECENT) // already sorted - let threeTransactions = Array(slice) + let _ = symLog.log("recent transactions") TransactionsArraySliceV(symLog: symLog, stack: stack.push(), currencyInfo: $currencyInfo, scopeInfo: scopeInfo, - transactions: threeTransactions, + transactions: $recentTransactions, + reloadAllAction: loadRecent, reloadOneAction: reloadOneAction) .padding(.leading, ICONLEADING) } header: { if !minimalistic { - let count = transactionCount > MAXRECENT ? MAXRECENT : transactionCount - Text(count > 1 ? "Recent \(count) transactions" - : "Recent transaction") + let recentHeader = recentTransactions.count > 1 + ? String(localized: "Recent transactions", comment: "section header plural") + : String(localized: "Recent transaction", comment: "section header singular") + Text(recentHeader) .talerFont(.title3) .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast)) } @@ -192,7 +202,7 @@ fileprivate struct CurrenciesNavigationLinksV: View { currencyInfo: $currencyInfo, navTitle: String(localized: "Transactions", comment: "ViewTitle of TransactionList"), scopeInfo: scopeInfo, - transactions: completedTransactions, + transactions: $completedTransactions, reloadAllAction: reloadAllAction, reloadOneAction: reloadOneAction) } diff --git a/TalerWallet1/Views/Transactions/TransactionsListView.swift b/TalerWallet1/Views/Transactions/TransactionsListView.swift @@ -20,7 +20,8 @@ struct TransactionsListView: View { let navTitle: String let scopeInfo: ScopeInfo - let transactions: [Transaction] + @Binding var transactions: [Transaction] + let reloadAllAction: (_ stack: CallStack) async -> () let reloadOneAction: ((_ transactionId: String, _ viewHandles: Bool) async throws -> Transaction) @@ -39,7 +40,8 @@ struct TransactionsListView: View { stack: stack.push(), currencyInfo: $currencyInfo, scopeInfo: scopeInfo, - transactions: transactions, + transactions: $transactions, + reloadAllAction: reloadAllAction, reloadOneAction: reloadOneAction) .padding(.leading, ICONLEADING) } @@ -67,7 +69,7 @@ struct TransactionsListView: View { .navigationTitle(navTitle) .accessibilityHint(String(localized: "Transaction list")) .task { - symLog.log(".task ") + symLog.log("❗️.task List❗️") await reloadAllAction(stack.push()) } .overlay { @@ -87,10 +89,12 @@ struct TransactionsArraySliceV: View { let stack: CallStack @Binding var currencyInfo: CurrencyInfo let scopeInfo: ScopeInfo - let transactions: [Transaction] + @Binding var transactions: [Transaction] + let reloadAllAction: (_ stack: CallStack) async -> () let reloadOneAction: ((_ transactionId: String, _ viewHandles: Bool) async throws -> Transaction) @EnvironmentObject private var model: WalletModel + var body: some View { #if PRINT_CHANGES let _ = Self._printChanges() @@ -101,25 +105,38 @@ struct TransactionsArraySliceV: View { let failAction = model.failTransaction let suspendAction = model.suspendTransaction let resumeAction = model.resumeTransaction - ForEach(Array(zip(transactions.indices, transactions)), id: \.1) { index, transaction in - NavigationLink { - LazyView { - TransactionSummaryV(stack: stack.push(), - currencyInfo: $currencyInfo, - transactionId: transaction.id, - reloadAction: reloadOneAction, - navTitle: nil, - hasDone: false, - abortAction: abortAction, - deleteAction: deleteAction, - failAction: failAction, - suspendAction: suspendAction, - resumeAction: resumeAction) - } - } label: { + + ForEach(transactions, id: \.self) { transaction in + let destination = TransactionSummaryV(stack: stack.push(), + currencyInfo: $currencyInfo, + transactionId: transaction.id, + reloadAction: reloadOneAction, + navTitle: nil, + hasDone: false, + abortAction: abortAction, + deleteAction: deleteAction, + failAction: failAction, + suspendAction: suspendAction, + resumeAction: resumeAction) + let row = NavigationLink { destination } label: { TransactionRowView(currencyInfo: $currencyInfo, transaction: transaction) + }.id(transaction.id) + if transaction.isDeleteable { + row.swipeActions(edge: .trailing) { + Button { + symLog?.log("deleteAction") + Task { // runs on MainActor + let _ = try? await deleteAction(transaction.id, false) + await reloadAllAction(stack.push()) + } + } label: { + Label("Delete", systemImage: "trash") + } + .tint(.red) + } + } else { + row } - .id(Int(index)) } } }