taler-ios

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

commit 185b6c0ad904af203233f8960b1116ebdeb332b1
parent 6d1028c0b4fceeafd3a890850ed4ebc20dde19cf
Author: Marc Stibane <marc@taler.net>
Date:   Wed, 21 Jun 2023 15:07:17 +0200

Suspend-Resume

Diffstat:
MTalerWallet1/Backend/Transaction.swift | 12++++++------
MTalerWallet1/Model/Model+Exchange.swift | 2+-
MTalerWallet1/Model/Model+Transactions.swift | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
MTalerWallet1/Model/WalletModel.swift | 2+-
MTalerWallet1/Views/Balances/BalancesSectionView.swift | 16++++++++++++----
MTalerWallet1/Views/Balances/UncompletedRowView.swift | 18+++++++++---------
MTalerWallet1/Views/Transactions/TransactionDetailView.swift | 25++++++++++++++-----------
MTalerWallet1/Views/Transactions/TransactionsListView.swift | 23++++++++++++++++++-----
8 files changed, 118 insertions(+), 49 deletions(-)

diff --git a/TalerWallet1/Backend/Transaction.swift b/TalerWallet1/Backend/Transaction.swift @@ -102,12 +102,12 @@ struct TransactionCommon: Decodable { case payment case refund case refresh - case reward // get paid for e.g. survey participation -// case tip // tip personnel at restaurants - case peerPushDebit = "peer-push-debit" // send coins to peer, show QR - case scanPushCredit = "peer-push-credit" // scan QR, receive coins from peer - case peerPullCredit = "peer-pull-credit" // request payment from peer, show QR - case scanPullDebit = "peer-pull-debit" // scan QR, pay requested + case reward = "tip" // get paid for e.g. survey participation +// case tip // tip personnel at restaurants + case peerPushDebit = "peer-push-debit" // send coins to peer, show QR + case scanPushCredit = "peer-push-credit" // scan QR, receive coins from peer + case peerPullCredit = "peer-pull-credit" // request payment from peer, show QR + case scanPullDebit = "peer-pull-debit" // scan QR, pay requested } var type: TransactionType var txState: TransactionState diff --git a/TalerWallet1/Model/Model+Exchange.swift b/TalerWallet1/Model/Model+Exchange.swift @@ -98,7 +98,7 @@ extension WalletModel { async throws { symLog?.log("adding exchange: \(url)") // TODO: .notice let request = AddExchange(exchangeBaseUrl: url) - logger.info("adding exchange: \(url)") + logger.info("adding exchange: \(url, privacy: .public)") _ = try await sendRequest(request) } } diff --git a/TalerWallet1/Model/Model+Transactions.swift b/TalerWallet1/Model/Model+Transactions.swift @@ -41,7 +41,6 @@ fileprivate struct GetTransactions: WalletBackendFormattedRequest { var currency: String? var search: String? - struct Args: Encodable { var currency: String? var search: String? @@ -53,16 +52,14 @@ fileprivate struct GetTransactions: WalletBackendFormattedRequest { } /// A request to abort a wallet transaction by ID. struct AbortTransaction: WalletBackendFormattedRequest { + struct Response: Decodable {} func operation() -> String { return "abortTransaction" } func args() -> Args { return Args(transactionId: transactionId) } var transactionId: String - struct Args: Encodable { var transactionId: String } - - struct Response: Decodable {} } /// A request to delete a wallet transaction by ID. struct DeleteTransaction: WalletBackendFormattedRequest { @@ -71,50 +68,98 @@ struct DeleteTransaction: WalletBackendFormattedRequest { func args() -> Args { return Args(transactionId: transactionId) } var transactionId: String + struct Args: Encodable { + var transactionId: String + } +} +/// A request to suspend a wallet transaction by ID. +struct SuspendTransaction: WalletBackendFormattedRequest { + struct Response: Decodable {} + func operation() -> String { return "suspendTransaction" } + func args() -> Args { return Args(transactionId: transactionId) } + var transactionId: String struct Args: Encodable { var transactionId: String } } +/// A request to suspend a wallet transaction by ID. +struct ResumeTransaction: WalletBackendFormattedRequest { + struct Response: Decodable {} + func operation() -> String { return "resumeTransaction" } + func args() -> Args { return Args(transactionId: transactionId) } + var transactionId: String + struct Args: Encodable { + var transactionId: String + } +} // MARK: - extension WalletModel { /// ask wallet-core for its list of transactions filtered by searchString func fetchTransactionsT(currency: String? = nil, searchString: String? = nil) - async -> [Transaction] { // might be called from a background thread itself + async -> [Transaction] { // might be called from a background thread itself do { let request = GetTransactions(currency: currency, search: searchString) + logger.info("getTransactions") let response = try await sendRequest(request, ASYNCDELAY) return response.transactions } catch { + logger.error("getTransactions failed: \(error)") return [] } } /// fetch transactions from Wallet-Core. No networking involved @MainActor func fetchTransactionsM(currency: String? = nil, searchString: String? = nil) - async -> [Transaction] { // M for MainActor - await fetchTransactionsT(currency: currency, searchString: searchString) + async -> [Transaction] { // M for MainActor + return await fetchTransactionsT(currency: currency, searchString: searchString) } func abortTransactionT(transactionId: String) - async throws { // might be called from a background thread itself + async throws { // might be called from a background thread itself let request = AbortTransaction(transactionId: transactionId) + logger.info("abortTransaction: \(transactionId, privacy: .private(mask: .hash))") let _ = try await sendRequest(request, ASYNCDELAY) } /// delete the specified transaction from Wallet-Core. No networking involved @MainActor func abortTransactionM(transactionId: String) - async throws { // M for MainActor - try await abortTransactionT(transactionId: transactionId) // call abortTransaction on main thread + async throws { // M for MainActor + try await abortTransactionT(transactionId: transactionId) } func deleteTransactionT(transactionId: String) - async throws { // might be called from a background thread itself + async throws { // might be called from a background thread itself let request = DeleteTransaction(transactionId: transactionId) + logger.info("deleteTransaction: \(transactionId, privacy: .private(mask: .hash))") let _ = try await sendRequest(request, ASYNCDELAY) } /// delete the specified transaction from Wallet-Core. No networking involved @MainActor func deleteTransactionM(transactionId: String) async throws { // M for MainActor - try await deleteTransactionT(transactionId: transactionId) // call deleteTransaction on main thread + try await deleteTransactionT(transactionId: transactionId) + } + + func suspendTransactionT(transactionId: String) + async throws { // might be called from a background thread itself + let request = SuspendTransaction(transactionId: transactionId) + logger.info("suspendTransaction: \(transactionId, privacy: .private(mask: .hash))") + let _ = try await sendRequest(request, ASYNCDELAY) + } + /// delete the specified transaction from Wallet-Core. No networking involved + @MainActor func suspendTransactionM(transactionId: String) + async throws { // M for MainActor + try await suspendTransactionT(transactionId: transactionId) + } + + func resumeTransactionT(transactionId: String) + async throws { // might be called from a background thread itself + let request = ResumeTransaction(transactionId: transactionId) + logger.info("resumeTransaction: \(transactionId, privacy: .private(mask: .hash))") + let _ = try await sendRequest(request, ASYNCDELAY) + } + /// delete the specified transaction from Wallet-Core. No networking involved + @MainActor func resumeTransactionM(transactionId: String) + async throws { // M for MainActor + try await resumeTransactionT(transactionId: transactionId) } } diff --git a/TalerWallet1/Model/WalletModel.swift b/TalerWallet1/Model/WalletModel.swift @@ -13,7 +13,7 @@ fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging // MARK: - /// The "virtual" base class for all models class WalletModel: ObservableObject { - public static let shared = WalletModel(-1) + public static let shared = WalletModel(0) static func className() -> String {"\(self)"} var symLog: SymLogC? let logger = Logger (subsystem: "net.taler.gnu", category: "wallet-core") diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift b/TalerWallet1/Views/Balances/BalancesSectionView.swift @@ -77,6 +77,8 @@ struct BalancesSectionView: View { } let deleteAction = model.deleteTransactionT // dummyTransaction let abortAction = model.abortTransactionT + let suspendAction = model.suspendTransactionT // dummyTransaction + let resumeAction = model.resumeTransactionT Section { // if "KUDOS" == currency && !balance.available.isZero { @@ -104,7 +106,9 @@ struct BalancesSectionView: View { reloadAllAction: reloadCompleted, reloadOneAction: reloadOneAction, deleteAction: deleteAction, - abortAction: abortAction) + abortAction: abortAction, + suspendAction: suspendAction, + resumeAction: resumeAction) }, tag: 3, selection: $buttonSelected ) { EmptyView() }.frame(width: 0).opacity(0).hidden() @@ -131,7 +135,9 @@ struct BalancesSectionView: View { reloadAllAction: reloadPending, reloadOneAction: reloadOneAction, deleteAction: deleteAction, - abortAction: abortAction) + abortAction: abortAction, + suspendAction: suspendAction, + resumeAction: resumeAction) } } label: { VStack(spacing: 6) { @@ -160,10 +166,12 @@ struct BalancesSectionView: View { reloadAllAction: reloadUncompleted, reloadOneAction: reloadOneAction, deleteAction: deleteAction, - abortAction: abortAction) + abortAction: abortAction, + suspendAction: suspendAction, + resumeAction: resumeAction) } } label: { - UncompletedRowView(uncompletedTransactions: uncompletedTransactions) + UncompletedRowView(uncompletedTransactions: $uncompletedTransactions) } } diff --git a/TalerWallet1/Views/Balances/UncompletedRowView.swift b/TalerWallet1/Views/Balances/UncompletedRowView.swift @@ -7,7 +7,7 @@ import taler_swift /// This view shows an uncompleted transaction row in a currency section struct UncompletedRowView: View { - var uncompletedTransactions: [Transaction] + @Binding var uncompletedTransactions: [Transaction] var body: some View { let count = uncompletedTransactions.count @@ -23,12 +23,12 @@ struct UncompletedRowView: View { } // MARK: - #if DEBUG -struct UncompletedRowView_Previews: PreviewProvider { - static var previews: some View { - let uncompletedTransactions: [Transaction] = [] - List { - UncompletedRowView(uncompletedTransactions: uncompletedTransactions) - } - } -} +//struct UncompletedRowView_Previews: PreviewProvider { +// static var previews: some View { +// let uncompletedTransactions: [Transaction] = [] +// List { +// UncompletedRowView(uncompletedTransactions: uncompletedTransactions) +// } +// } +//} #endif diff --git a/TalerWallet1/Views/Transactions/TransactionDetailView.swift b/TalerWallet1/Views/Transactions/TransactionDetailView.swift @@ -32,6 +32,16 @@ struct TransactionDetailView: View { : localizedType Group { List { + if developerMode { + if transaction.isSuspendable { if let suspendAction { + TransactionButton(transactionId: common.transactionId, + command: .suspend, action: suspendAction) + } } + if transaction.isResumable { if let resumeAction { + TransactionButton(transactionId: common.transactionId, + command: .resume, action: resumeAction) + } } + } Text("\(dateString)") .font(.title2) // .listRowSeparator(.hidden) @@ -49,16 +59,6 @@ struct TransactionDetailView: View { if let doneAction { DoneButton(doneAction: doneAction) } - if developerMode { - if transaction.isSuspendable { if let suspendAction { - TransactionButton(transactionId: common.transactionId, - command: .suspend, action: suspendAction) - } } - if transaction.isResumable { if let resumeAction { - TransactionButton(transactionId: common.transactionId, - command: .resume, action: resumeAction) - } } - } }.listStyle(myListStyle.style).anyView }.onNotification(.TransactionStateTransition) { notification in if let transition = notification.userInfo?[TRANSACTIONTRANSITION] as? TransactionTransition { @@ -71,6 +71,7 @@ struct TransactionDetailView: View { symLog.log("newState: \(newState), reloading transaction") let reloadedTransaction = try await reloadAction(common.transactionId) transaction = reloadedTransaction // redraw + symLog.log("reloaded transaction: \(reloadedTransaction.common.txState.major)") } catch { symLog.log(error.localizedDescription) }}} @@ -150,7 +151,9 @@ struct TransactionDetailView: View { baseURL: nil, large: true) // TODO: baseURL case .peer2peer(let p2pTransaction): let details = p2pTransaction.details // TODO: details - QRCodeDetails(transaction: transaction) + if pending { + QRCodeDetails(transaction: transaction) + } ThreeAmounts(common: common, topTitle: String(localized: "Peer to Peer:"), baseURL: details.exchangeBaseUrl, large: false) } diff --git a/TalerWallet1/Views/Transactions/TransactionsListView.swift b/TalerWallet1/Views/Transactions/TransactionsListView.swift @@ -6,7 +6,7 @@ import SwiftUI import SymLog struct TransactionsListView: View { - private let symLog = SymLogV() + private let symLog = SymLogV(0) @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic let navTitle: String @@ -16,6 +16,8 @@ struct TransactionsListView: View { let reloadOneAction: ((_ transactionId: String) async throws -> Transaction) let deleteAction: (_ transactionId: String) async throws -> () let abortAction: (_ transactionId: String) async throws -> () + let suspendAction: (_ transactionId: String) async throws -> () + let resumeAction: (_ transactionId: String) async throws -> () var body: some View { #if DEBUG @@ -26,9 +28,16 @@ struct TransactionsListView: View { // TODO: Unlock the power of grammatical agreement // let title = AttributedString(localized: "^[\(count) Ticket](inflect: true)") let title: String = "\(count) \(navTitle)" - Content(symLog: symLog, currency: currency, transactions: transactions, myListStyle: $myListStyle, - reloadAllAction: reloadAllAction, reloadOneAction: reloadOneAction, - deleteAction: deleteAction, abortAction: abortAction) + Content(symLog: symLog, + currency: currency, + transactions: transactions, + myListStyle: $myListStyle, + reloadAllAction: reloadAllAction, + reloadOneAction: reloadOneAction, + deleteAction: deleteAction, + abortAction: abortAction, + suspendAction: suspendAction, + resumeAction: resumeAction) .navigationTitle(title) .navigationBarTitleDisplayMode(.large) // .inline .onAppear { @@ -51,6 +60,8 @@ extension TransactionsListView { let reloadOneAction: ((_ transactionId: String) async throws -> Transaction) let deleteAction: (_ transactionId: String) async throws -> () let abortAction: (_ transactionId: String) async throws -> () + let suspendAction: (_ transactionId: String) async throws -> () + let resumeAction: (_ transactionId: String) async throws -> () @State private var upAction: () -> Void = {} @State private var downAction: () -> Void = {} @@ -92,7 +103,9 @@ extension TransactionsListView { TransactionDetailView(transaction: transaction, reloadAction: reloadOneAction, deleteAction: deleteAction, - abortAction: abortAction) + abortAction: abortAction, + suspendAction: suspendAction, + resumeAction: resumeAction) }} label: { TransactionRowView(transaction: transaction) }