taler-ios

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

commit e1d95f754404192ae906595ccf77e0e2e7d01a34
parent 01cdde738f910580511e586899e6d0c151d08cfc
Author: Marc Stibane <marc@taler.net>
Date:   Mon, 25 Nov 2024 21:00:18 +0100

BankListView

Diffstat:
MTalerWallet.xcodeproj/project.pbxproj | 20++++++++++++++++++++
ATalerWallet1/Views/Settings/Bank/BankListView.swift | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ATalerWallet1/Views/Settings/Bank/BankSectionView.swift | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MTalerWallet1/Views/Settings/SettingsView.swift | 9+++++++++
4 files changed, 254 insertions(+), 0 deletions(-)

diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj @@ -11,6 +11,10 @@ 4E0A71152C396D86002485BB /* Error+debugDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0A71132C396D86002485BB /* Error+debugDescription.swift */; }; 4E0A71182C3AB099002485BB /* IconBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0A71172C3AB099002485BB /* IconBadge.swift */; }; 4E0A71192C3AB099002485BB /* IconBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0A71172C3AB099002485BB /* IconBadge.swift */; }; + 4E0E8EFA2CEFDB0700C5F914 /* BankListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0E8EF92CEFDB0700C5F914 /* BankListView.swift */; }; + 4E0E8EFB2CEFDB0700C5F914 /* BankListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0E8EF92CEFDB0700C5F914 /* BankListView.swift */; }; + 4E0E8EFD2CF0A18800C5F914 /* BankSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0E8EFC2CF0A18800C5F914 /* BankSectionView.swift */; }; + 4E0E8EFE2CF0A18800C5F914 /* BankSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0E8EFC2CF0A18800C5F914 /* BankSectionView.swift */; }; 4E11803D2CD2A6D700023E37 /* SendAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E11803C2CD2A6D700023E37 /* SendAmountView.swift */; }; 4E11803E2CD2A6D700023E37 /* SendAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E11803C2CD2A6D700023E37 /* SendAmountView.swift */; }; 4E16E12329F3BB99008B9C86 /* CurrencySpecification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /* CurrencySpecification.swift */; }; @@ -362,6 +366,8 @@ /* Begin PBXFileReference section */ 4E0A71132C396D86002485BB /* Error+debugDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+debugDescription.swift"; sourceTree = "<group>"; }; 4E0A71172C3AB099002485BB /* IconBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconBadge.swift; sourceTree = "<group>"; }; + 4E0E8EF92CEFDB0700C5F914 /* BankListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BankListView.swift; sourceTree = "<group>"; }; + 4E0E8EFC2CF0A18800C5F914 /* BankSectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BankSectionView.swift; sourceTree = "<group>"; }; 4E11803C2CD2A6D700023E37 /* SendAmountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendAmountView.swift; sourceTree = "<group>"; }; 4E16E12229F3BB99008B9C86 /* CurrencySpecification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencySpecification.swift; sourceTree = "<group>"; }; 4E1A59E02C99C5D700842BBF /* View+Keyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Keyboard.swift"; sourceTree = "<group>"; }; @@ -571,6 +577,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 4E0E8EF82CEFDADF00C5F914 /* Bank */ = { + isa = PBXGroup; + children = ( + 4E0E8EF92CEFDB0700C5F914 /* BankListView.swift */, + 4E0E8EFC2CF0A18800C5F914 /* BankSectionView.swift */, + ); + path = Bank; + sourceTree = "<group>"; + }; 4E2254942A822B8100E41D29 /* Sounds */ = { isa = PBXGroup; children = ( @@ -769,6 +784,7 @@ 4EB095242989CBFE0043A8A1 /* Settings */ = { isa = PBXGroup; children = ( + 4E0E8EF82CEFDADF00C5F914 /* Bank */, 4EB095252989CBFE0043A8A1 /* SettingsView.swift */, 4EC400882AE3E7E800DF72C7 /* AboutView.swift */, 4EB095262989CBFE0043A8A1 /* SettingsItem.swift */, @@ -1259,6 +1275,7 @@ 4E3EAE3C2A990778009F1BE8 /* WithdrawTOSView.swift in Sources */, 4E3EAE3D2A990778009F1BE8 /* Sheet.swift in Sources */, 4E3EAE3E2A990778009F1BE8 /* ManualDetailsV.swift in Sources */, + 4E0E8EFA2CEFDB0700C5F914 /* BankListView.swift in Sources */, 4E3EAE3F2A990778009F1BE8 /* View+dismissTop.swift in Sources */, 4E3EAE402A990778009F1BE8 /* TransactionsListView.swift in Sources */, 4EE9864F2CE26E0F00F75634 /* DepositAmountView.swift in Sources */, @@ -1328,6 +1345,7 @@ 4E3EAE6F2A990778009F1BE8 /* TalerStrings.swift in Sources */, 4E3EAE702A990778009F1BE8 /* CurrencyInputView.swift in Sources */, 4E3EAE712A990778009F1BE8 /* URL+id+iban.swift in Sources */, + 4E0E8EFD2CF0A18800C5F914 /* BankSectionView.swift in Sources */, 4ED80E8B2B8F60E7008BD576 /* Atomic.swift in Sources */, 4EEC3A712B2285A200D05F9D /* WithdrawExchangeV.swift in Sources */, 4EDBDCD92AB787CB00925C02 /* CallStack.swift in Sources */, @@ -1395,6 +1413,7 @@ 4EB095652989CBFE0043A8A1 /* WithdrawTOSView.swift in Sources */, 4EEC157829F9032900D46A03 /* Sheet.swift in Sources */, 4E6EDD852A3615BE0031D520 /* ManualDetailsV.swift in Sources */, + 4E0E8EFB2CEFDB0700C5F914 /* BankListView.swift in Sources */, 4EB0950B2989CB7C0043A8A1 /* View+dismissTop.swift in Sources */, 4EB095562989CBFE0043A8A1 /* TransactionsListView.swift in Sources */, 4EE986502CE26E0F00F75634 /* DepositAmountView.swift in Sources */, @@ -1464,6 +1483,7 @@ 4EB0950A2989CB7C0043A8A1 /* TalerStrings.swift in Sources */, 4EA551252A2C923600FEC9A8 /* CurrencyInputView.swift in Sources */, 4E363CBC2A237E0900D7E98C /* URL+id+iban.swift in Sources */, + 4E0E8EFE2CF0A18800C5F914 /* BankSectionView.swift in Sources */, 4ED80E8C2B8F60E7008BD576 /* Atomic.swift in Sources */, 4EEC3A722B2285A200D05F9D /* WithdrawExchangeV.swift in Sources */, 4EDBDCDA2AB787CB00925C02 /* CallStack.swift in Sources */, diff --git a/TalerWallet1/Views/Settings/Bank/BankListView.swift b/TalerWallet1/Views/Settings/Bank/BankListView.swift @@ -0,0 +1,101 @@ +/* + * This file is part of GNU Taler, ©2022-24 Taler Systems S.A. + * See LICENSE.md + */ +/** + * @author Marc Stibane + */ +import SwiftUI +import taler_swift +import SymLog + +/// This view shows the list of exchanges +struct BankListView: View { + private let symLog = SymLogV(0) + let stack: CallStack + let navTitle: String + + @EnvironmentObject private var model: WalletModel + @EnvironmentObject private var controller: Controller + @AppStorage("minimalistic") var minimalistic: Bool = false + @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic + + @State var showAlert: Bool = false + @State var newExchange: String = TESTEXCHANGE + @State private var bankAccounts: [KnownBankAccountsInfo] = [] + + @MainActor + func addExchange(_ exchange: String) -> Void { + Task { // runs on MainActor + symLog.log("adding: \(exchange)") + if let _ = try? await model.addExchange(url: exchange) { + symLog.log("added: \(exchange)") + announce("added: \(exchange)") + NotificationCenter.default.post(name: .ExchangeAdded, object: nil, userInfo: nil) + } + } + } + + @MainActor + private func viewDidLoad() async { + if let accounts = try? await model.listKnownBankAccounts() { + withAnimation { bankAccounts = accounts } + } + } + + var body: some View { +#if PRINT_CHANGES + let _ = Self._printChanges() + let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear +#endif + let a11yLabelStr = String(localized: "Add bank account", comment: "VoiceOver for the + button") + let plusButton = PlusButton(accessibilityLabelStr: a11yLabelStr) { + showAlert = true + } + let addTitleStr = String(localized: "Add bank account", comment: "title of the addExchange alert") + let addButtonStr = String(localized: "Add", comment: "button in the addExchange alert") + + let sortedList = List(bankAccounts, id: \.self) { account in + BankSectionView(stack: stack.push(), + account: account) + } + + let emptyList = List { + Section { + Text("There are no bank accounts yet.") + .talerFont(.title3) + } + Section { + let plus = Image(systemName: "plus") + if !minimalistic { + Text("Tap the \(plus) button to add an account.") + .listRowSeparator(.hidden) + Text("You can also scan a withdrawal QR code from your bank in the Action menu to automatically add a bank account.") + } else { + Text("Tap \(plus) or scan a withdrawal QR code from your bank to add a bank account.") + } + } + .talerFont(.body) + } + + Group { + if bankAccounts.isEmpty { + emptyList + } else { + sortedList + } + } + .listStyle(myListStyle.style).anyView + .refreshable { + controller.hapticNotification(.success) + symLog.log("refreshing") + await viewDidLoad() + } + .task { await viewDidLoad() } + .navigationTitle(navTitle) + .navigationBarItems(trailing: plusButton) + .onAppear() { + DebugViewC.shared.setViewID(VIEW_PAYMENT_SERVICES, stack: stack.push()) + } + } // body +} diff --git a/TalerWallet1/Views/Settings/Bank/BankSectionView.swift b/TalerWallet1/Views/Settings/Bank/BankSectionView.swift @@ -0,0 +1,124 @@ +/* + * This file is part of GNU Taler, ©2022-24 Taler Systems S.A. + * See LICENSE.md + */ +/** + * @author Marc Stibane + */ +import SwiftUI +import taler_swift +import SymLog + +/// This view shows the currency name in an exchange section +/// currency +struct BankSectionView: View { + private let symLog = SymLogV(0) + let stack: CallStack + let account: KnownBankAccountsInfo + + @EnvironmentObject private var model: WalletModel + @EnvironmentObject private var controller: Controller + @AppStorage("minimalistic") var minimalistic: Bool = false + @AppStorage("demoHints") var demoHints: Bool = true + @AppStorage("fakeNoFees") var fakeNoFees: Bool = true + + @State private var shouldReloadBalances: Int = 0 + @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN) + @State private var didDelete: Bool = false + @State private var disabled: Bool = false + @State private var showAlert: Bool = false + @State private var purge: Bool = false + @State private var global: Bool = false + + @MainActor + private func viewDidLoad() async { +// if let exc = try? await model.listExchanges(scope: balance.scopeInfo) { +// withAnimation { exchanges = exc } +// } + } + + @MainActor + private func deleteAccount() { + disabled = true // don't try this more than once + Task { // runs on MainActor +// if let _ = try? await model.deleteExchange(url: baseUrl, purge: purge, viewHandles: !purge) { +// purge = false +// symLog.log("deleted \(baseUrl.trimURL)") +// didDelete = true // change button text +// NotificationCenter.default.post(name: .ExchangeDeleted, object: nil, userInfo: nil) +// NotificationCenter.default.post(name: .BalanceChange, object: nil, userInfo: nil) +// demoHints = true +// } else { +// purge = true +// showAlert = true +// disabled = false +// } + } + } + + var body: some View { +#if PRINT_CHANGES + let _ = Self._printChanges() +// let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear +#endif + Section { + if let alias = account.alias { + Text(alias) + } + let payURL = URL(string: account.paytoUri) + if let queryParameters = payURL?.queryParameters { + let name = if let rcv = queryParameters["receiver-name"] { + rcv.replacingOccurrences(of: "+", with: " ") + } else { + EMPTYSTRING + } + Text("Holder: \(name)") + + +// let amountStr = queryParameters["amount"] ?? EMPTYSTRING +// let messageStr = queryParameters["message"] ?? EMPTYSTRING +// let senderStr = queryParameters["sender-name"] ?? EMPTYSTRING + + } + + let method = if let iban = payURL?.iban { + Text("IBAN: \(iban)") + } else if let xTaler = payURL?.xTaler { + Text("TALER: \(xTaler)") + } else { + Text("unknown payment method") + } + method + + let kyc = account.kycCompleted ? String(localized: "verified") + : String(localized: "not yet verified") + Text(kyc) + .padding(.leading) + + if account.currencies.count == 1 { + let currency = account.currencies[0] + Text("Currency: \(currency)") + } else { + Text("Currencies:") + ForEach (account.currencies, id: \.self) { currency in + Text(currency) + .padding(.leading) + } + } + Button { + deleteAccount() + } label: { + Label("Delete Account", systemImage: "trash") + } + .buttonStyle(TalerButtonStyle(type: .bordered, disabled: disabled)) + .disabled(disabled) + } header: { + } + .task { await viewDidLoad() } +// .task(id: controller.currencyTicker) { await currencyTickerChanged(scopeInfo) } + .onDisappear() { + disabled = false + purge = false + } + } +} diff --git a/TalerWallet1/Views/Settings/SettingsView.swift b/TalerWallet1/Views/Settings/SettingsView.swift @@ -107,6 +107,15 @@ struct SettingsView: View { SettingsItem(name: exchangesTitle, id1: "exchanges", description: hideDescriptions ? nil : String(localized: "Manage payment services...")) {} } + let bankAccountsTitle = String(localized: "TitleBankAccounts", defaultValue: "Bank Accounts") + let bankAccountsDest = BankListView(stack: stack.push(bankAccountsTitle), + navTitle: bankAccountsTitle) + NavigationLink { // whole row like in a tableView + bankAccountsDest + } label: { + SettingsItem(name: bankAccountsTitle, id1: "bankAccounts", + description: hideDescriptions ? nil : String(localized: "Your accounts for deposit...")) {} + } SettingsToggle(name: String(localized: "Minimalistic"), value: $minimalistic, id1: "minimal", description: hideDescriptions ? nil : String(localized: "Omit text where possible")) { hideDescriptions = minimalistic //withAnimation { hideDescriptions = minimalistic }