taler-ios

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

commit c98215fcf19b591d3716a79d53b366d3d03d8d62
parent 73abd7b5382629702ddd4feb1038bb40ef2d9fa6
Author: Marc Stibane <marc@taler.net>
Date:   Fri, 31 May 2024 09:08:07 +0200

WithdrawAcceptView - prepare for b-i-withdrawals without amount

Diffstat:
MTalerWallet.xcodeproj/project.pbxproj | 6++++++
MTalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptView.swift | 174+++++++++++++++++++++++++++++++++++++++----------------------------------------
MTalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift | 151+++++++++++++++++++++++++++++++++++++++----------------------------------------
3 files changed, 165 insertions(+), 166 deletions(-)

diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj @@ -259,6 +259,8 @@ 4EE171922B49FE4E00BF9FF5 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 4EE171912B49FE4E00BF9FF5 /* OrderedCollections */; }; 4EE77E7D2C0280E5007C9064 /* GNU_Taler InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 4EE77E7C2C0280E5007C9064 /* GNU_Taler InfoPlist.xcstrings */; }; 4EE77E7F2C0280E5007C9064 /* Taler_Wallet InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 4EE77E7E2C0280E5007C9064 /* Taler_Wallet InfoPlist.xcstrings */; }; + 4EE77E812C06E513007C9064 /* WithdrawAcceptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE77E802C06E513007C9064 /* WithdrawAcceptView.swift */; }; + 4EE77E822C06E513007C9064 /* WithdrawAcceptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE77E802C06E513007C9064 /* WithdrawAcceptView.swift */; }; 4EEC118D2B83DE4800146CFF /* AmountInputV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC118C2B83DE4700146CFF /* AmountInputV.swift */; }; 4EEC118E2B83DE4800146CFF /* AmountInputV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC118C2B83DE4700146CFF /* AmountInputV.swift */; }; 4EEC11932B83FB7A00146CFF /* SubjectInputV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC11922B83FB7A00146CFF /* SubjectInputV.swift */; }; @@ -452,6 +454,7 @@ 4EDBDCD82AB787CB00925C02 /* CallStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallStack.swift; sourceTree = "<group>"; }; 4EE77E7C2C0280E5007C9064 /* GNU_Taler InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = "GNU_Taler InfoPlist.xcstrings"; sourceTree = "<group>"; }; 4EE77E7E2C0280E5007C9064 /* Taler_Wallet InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = "Taler_Wallet InfoPlist.xcstrings"; sourceTree = "<group>"; }; + 4EE77E802C06E513007C9064 /* WithdrawAcceptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WithdrawAcceptView.swift; sourceTree = "<group>"; }; 4EEC118C2B83DE4700146CFF /* AmountInputV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmountInputV.swift; sourceTree = "<group>"; }; 4EEC11922B83FB7A00146CFF /* SubjectInputV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubjectInputV.swift; sourceTree = "<group>"; }; 4EEC11952B840F1100146CFF /* PayTemplateV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayTemplateV.swift; sourceTree = "<group>"; }; @@ -779,6 +782,7 @@ isa = PBXGroup; children = ( 4EB0953C2989CBFE0043A8A1 /* WithdrawURIView.swift */, + 4EE77E802C06E513007C9064 /* WithdrawAcceptView.swift */, 4E5A88F62A3B9E5B00072618 /* WithdrawAcceptDone.swift */, 4EB095402989CBFE0043A8A1 /* WithdrawTOSView.swift */, ); @@ -1156,6 +1160,7 @@ 4E3EAE342A990778009F1BE8 /* SuperScriptDigits.swift in Sources */, 4E3EAE352A990778009F1BE8 /* P2pPayURIView.swift in Sources */, 4E3EAE362A990778009F1BE8 /* Model+Payment.swift in Sources */, + 4EE77E812C06E513007C9064 /* WithdrawAcceptView.swift in Sources */, 4E3EAE372A990778009F1BE8 /* SettingsView.swift in Sources */, 4E3EAE382A990778009F1BE8 /* PaymentView.swift in Sources */, 4EEC11962B840F1100146CFF /* PayTemplateV.swift in Sources */, @@ -1275,6 +1280,7 @@ 4EBA563F2A7FD9390084948B /* SuperScriptDigits.swift in Sources */, 4E578E942A4822D500F21F1C /* P2pPayURIView.swift in Sources */, 4EB095542989CBFE0043A8A1 /* Model+Payment.swift in Sources */, + 4EE77E822C06E513007C9064 /* WithdrawAcceptView.swift in Sources */, 4EB0954F2989CBFE0043A8A1 /* SettingsView.swift in Sources */, 4EB095552989CBFE0043A8A1 /* PaymentView.swift in Sources */, 4EEC11972B840F1100146CFF /* PayTemplateV.swift in Sources */, diff --git a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptView.swift b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawAcceptView.swift @@ -1,111 +1,107 @@ /* - * This file is part of GNU Taler, ©2022-23 Taler Systems S.A. + * 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 struct WithdrawAcceptView: View { - private let symLog = SymLogV(0) - let navTitle = String(localized: "Accept Withdrawal") + private let symLog = SymLogV() + let stack: CallStack + let navTitle = String(localized: "Withdrawal") - let exchangeBaseUrl: String - let model: WithdrawModel? - let amount: Amount? + // the URL from the bank website let url: URL + @Binding var amountToTransfer: Amount + @Binding var exchange: Exchange? - @State private var buttonSelected: Int? = nil - @State private var confirmTransferUrl: String? = nil - @State private var transactionId: String? = nil - @State var manualWithdrawalDetails: ManualWithdrawalDetails? + @EnvironmentObject private var controller: Controller + @EnvironmentObject private var model: WalletModel + @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic - func acceptAction() -> () { - Task { - do { - if let model { - if let acceptWithdrawalResponse = try await model.sendAcceptIntWithdrawalM(exchangeBaseUrl, withdrawURL: url.absoluteString) { - confirmTransferUrl = acceptWithdrawalResponse.confirmTransferUrl - transactionId = acceptWithdrawalResponse.transactionId - symLog.log(confirmTransferUrl ?? "❗️Yikes: No confirmTransferUrl") - buttonSelected = 1 // trigger NavigationLink - } else { - // TODO: error sendAcceptIntWithdrawal failed - } - } - } catch { // TODO: error - symLog.log(error.localizedDescription) - } - } - } + @State private var withdrawalAmountDetails: WithdrawalAmountDetails? = nil var body: some View { - VStack { - if let manualWithdrawalDetails { + if let exchange, let withdrawalAmountDetails { + VStack { + let tosAccepted = exchange.tosStatus == .accepted + if !tosAccepted { + ToSButtonView(stack: stack.push(), + exchangeBaseUrl: exchange.exchangeBaseUrl, + viewID: SHEET_WITHDRAW_TOS, + p2p: false) + } List { + let raw = withdrawalAmountDetails.amountRaw + let effective = withdrawalAmountDetails.amountEffective + let currency = raw.currencyStr + let currencyInfo = controller.info(for: currency, controller.currencyTicker) + let fee = try! Amount.diff(raw, effective) + let outColor = WalletColors().transactionColor(false) + let inColor = WalletColors().transactionColor(true) - HStack(spacing: 0) { - NavigationLink(destination: LazyView { - WithdrawAcceptDone(model: model, confirmTransferUrl: confirmTransferUrl, transactionId: transactionId) - }, tag: 1, selection: $buttonSelected - ) { EmptyView() }.frame(width: 0).opacity(0).hidden() - - ThreeAmountsView(topTitle: String(localized: "Chosen amount to withdraw:"), - topAmount: raw, fee: fee, - bottomTitle: String(localized: "Coins to be withdrawn:"), - bottomAmount: effective, - large: false, pending: false, incoming: true, - baseURL: exchangeBaseUrl) - } - } - .safeAreaInset(edge: .bottom) { - Button("Confirm Withdrawal", action: acceptAction) - .lineLimit(2) - .disabled(false) - .buttonStyle(TalerButtonStyle(type: .prominent, narrow: false, aligned: .center)) - .padding() + ThreeAmountsV(stack: stack.push(), + topTitle: String(localized: "Chosen amount to withdraw:"), + topAbbrev: String(localized: "Chosen:", comment: "mini"), + topAmount: raw, fee: fee, + bottomTitle: String(localized: "Amount to be withdrawn:"), + bottomAbbrev: String(localized: "Effective:", comment: "mini"), + bottomAmount: effective, + large: false, pending: false, incoming: true, + baseURL: exchange.exchangeBaseUrl, + noFees: exchange.noFees, + txStateLcl: nil, // common.txState.major.localizedState + summary: nil, + merchant: nil) + let someCoins = SomeCoins(details: withdrawalAmountDetails) + QuiteSomeCoins(someCoins: someCoins, + shouldShowFee: true, // TODO: set to false if we never charge withdrawal fees + currency: currency, + currencyInfo: currencyInfo, + amountEffective: effective) } + .listStyle(myListStyle.style).anyView .navigationTitle(navTitle) - } else { - WithdrawProgressView(message: exchangeBaseUrl.trimURL()) - .navigationTitle("Found Exchange") - } - } -// .overlay { -// VStack { -// ErrorView(errortext: "unknown state") // TODO: Error -// }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) -// .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) -// } - .onAppear() { - DebugViewC.shared.setSheetID(SHEET_WITHDRAW_ACCEPT) - } - .task { if let amount, let model { - do { // TODO: cancelled - symLog.log(".task") - if exchangeBaseUrl.hasPrefix(HTTPS) { - symLog.log("amount: \(amount), baseURL: \(String(describing: exchangeBaseUrl))") - // TODO: let user choose exchange from list - manualWithdrawalDetails = try await model.loadWithdrawalDetailsForAmountM(exchangeBaseUrl, amount: amount) - symLog.log("raw: \(manualWithdrawalDetails!.amountRaw), effective: \(manualWithdrawalDetails!.amountEffective)") - } else { - // TODO: error no exchange! + if tosAccepted { + NavigationLink(destination: LazyView { + WithdrawAcceptDone(stack: stack.push(), + exchangeBaseUrl: exchange.exchangeBaseUrl, + url: url) + }) { + Text("Confirm Withdrawal") // SHEET_WITHDRAW_ACCEPT + } + .buttonStyle(TalerButtonStyle(type: .prominent)) + .padding(.horizontal) } - } catch { // TODO: error - symLog.log(error.localizedDescription) } - } else { - // TODO: error no amount! - } } - } -} -// MARK: - -struct WithdrawAcceptView_Previews: PreviewProvider { - static var previews: some View { - let amount = try! Amount(fromString: LONGCURRENCY + ":2.4") - WithdrawAcceptView(exchangeBaseUrl: DEMOEXCHANGE, - model: nil, - amount: amount, - url: URL(string: DEMOSHOP)!) + .onAppear() { + symLog.log("onAppear") + DebugViewC.shared.setSheetID(SHEET_WITHDRAW_ACCEPT) + } + } else { // no details or no exchange +#if DEBUG + let message = url.host +#else + let message: String? = nil +#endif + LoadingView(scopeInfo: nil, message: message) + .task(id: exchange) { + symLog.log(".task") + if let exchange { + if let details = try? await model.getWithdrawalDetailsForAmountM(exchange.exchangeBaseUrl, + amount: amountToTransfer) { + withdrawalAmountDetails = details + } +// agePicker.setAges(ages: details?.ageRestrictionOptions) + } else { // TODO: error + symLog.log("no exchangeBaseUrl or no exchange") + withdrawalAmountDetails = nil + } + } + } } } diff --git a/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift b/TalerWallet1/Views/Sheets/WithdrawBankIntegrated/WithdrawURIView.swift @@ -13,7 +13,7 @@ import SymLog // We show the user the bank-integrated withdrawal details in a sheet - but first the ToS must be accepted. // After the user confirmed the withdrawal, we show a button to return to the bank website to confirm there, too struct WithdrawURIView: View { - private let symLog = SymLogV(0) + private let symLog = SymLogV() let stack: CallStack let navTitle = String(localized: "Withdrawal") @@ -24,94 +24,91 @@ struct WithdrawURIView: View { @EnvironmentObject private var model: WalletModel @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic - @State private var withdrawalAmountDetails: WithdrawalAmountDetails? = nil + @State private var withdrawUriInfo: WithdrawUriInfoResponse? = nil + @State private var currencyName = EMPTYSTRING + @State private var amountIsEditable = false + @State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING) // Update currency when used + @State private var amountShortcut = Amount.zero(currency: EMPTYSTRING) // Update currency when used + @State private var shortcutSelected = false + + @State private var selectedExchange = EMPTYSTRING @State private var exchange: Exchange? = nil + @State private var possibleExchanges: [Exchange] = [] + @State private var defaultExchangeBaseUrl: String? // if nil then use possibleExchanges - var body: some View { - VStack { - if let withdrawalAmountDetails, let exchange { - let tosAccepted = exchange.tosStatus == .accepted - if !tosAccepted { - ToSButtonView(stack: stack.push(), - exchangeBaseUrl: exchange.exchangeBaseUrl, - viewID: SHEET_WITHDRAW_TOS, - p2p: false) - } - List { - let raw = withdrawalAmountDetails.amountRaw - let effective = withdrawalAmountDetails.amountEffective - let currency = raw.currencyStr - let currencyInfo = controller.info(for: currency, controller.currencyTicker) - let fee = try! Amount.diff(raw, effective) - let outColor = WalletColors().transactionColor(false) - let inColor = WalletColors().transactionColor(true) + func loadExchange(_ baseUrl: String) async { // TODO: throws? + if let someExchange = try? await model.getExchangeByUrl(url: baseUrl) { + exchange = someExchange + } + } - ThreeAmountsV(stack: stack.push(), - topTitle: String(localized: "Chosen amount to withdraw:"), - topAbbrev: String(localized: "Chosen:", comment: "mini"), - topAmount: raw, fee: fee, - bottomTitle: String(localized: "Amount to be withdrawn:"), - bottomAbbrev: String(localized: "Effective:", comment: "mini"), - bottomAmount: effective, - large: false, pending: false, incoming: true, - baseURL: exchange.exchangeBaseUrl, - noFees: exchange.noFees, - txStateLcl: nil, // common.txState.major.localizedState - summary: nil, - merchant: nil) - let someCoins = SomeCoins(details: withdrawalAmountDetails) - QuiteSomeCoins(someCoins: someCoins, - shouldShowFee: true, // TODO: set to false if we never charge withdrawal fees - currency: currency, - currencyInfo: currencyInfo, - amountEffective: effective) + var body: some View { + if possibleExchanges.count > 0 { + if let defaultBaseUrl = defaultExchangeBaseUrl ?? possibleExchanges.first?.exchangeBaseUrl { + VStack { + let title = String(localized: "Exchange") + if possibleExchanges.count > 1 { + Picker(title, selection: $selectedExchange) { + ForEach(possibleExchanges, id: \.self) { exchange in + let baseUrl = exchange.exchangeBaseUrl + Text(baseUrl) + } + } + .talerFont(.title3) + .pickerStyle(.menu) + .onAppear() { + withAnimation { selectedExchange = defaultBaseUrl } + } + .onChange(of: selectedExchange) { selected in + Task { + await loadExchange(selected) + } + } + } else { + Text(defaultBaseUrl) + .task { + await loadExchange(defaultBaseUrl) + } + } + if amountToTransfer.isZero { + // TODO: input amount, then + WithdrawAcceptView(stack: stack.push(), url: url, + amountToTransfer: $amountToTransfer, + exchange: $exchange) + } else { + WithdrawAcceptView(stack: stack.push(), url: url, + amountToTransfer: $amountToTransfer, + exchange: $exchange) + } } - .listStyle(myListStyle.style).anyView .navigationTitle(navTitle) - if tosAccepted { - NavigationLink(destination: LazyView { - WithdrawAcceptDone(stack: stack.push(), - exchangeBaseUrl: exchange.exchangeBaseUrl, - url: url) - }) { - Text("Confirm Withdrawal") // SHEET_WITHDRAW_ACCEPT - } - .buttonStyle(TalerButtonStyle(type: .prominent)) - .padding(.horizontal) + .onAppear() { + symLog.log("onAppear") + DebugViewC.shared.setSheetID(SHEET_WITHDRAWAL) } - } else { // no details or no exchange + // agePicker.setAges(ages: details?.ageRestrictionOptions) + } else { // TODO: error +// symLog.log("no exchangeBaseUrl or no exchange") + } + + + } else { // no details or no exchange #if DEBUG - let message = url.host + let message = url.host #else - let message: String? = nil + let message: String? = nil #endif - LoadingView(scopeInfo: nil, message: message) - } - } - .onAppear() { - symLog.log("onAppear") - DebugViewC.shared.setSheetID(SHEET_WITHDRAWAL) - } - .task { - symLog.log(".task") - if let withdrawUriInfo = try? await model.getWithdrawalDetailsForUriM(url.absoluteString) { - if let amount = withdrawUriInfo.amount { - let baseUrl = withdrawUriInfo.defaultExchangeBaseUrl - ?? withdrawUriInfo.possibleExchanges.first?.exchangeBaseUrl - if let baseUrl { - exchange = try? await model.getExchangeByUrl(url: baseUrl) - if let details = try? await model.getWithdrawalDetailsForAmountM(baseUrl, amount: amount) { - withdrawalAmountDetails = details - } - // agePicker.setAges(ages: details?.ageRestrictionOptions) - } else { // TODO: error - symLog.log("no exchangeBaseUrl or no exchange") - withdrawalAmountDetails = nil + LoadingView(scopeInfo: nil, message: message) + .task { + symLog.log(".task") + if let someInfo = try? await model.getWithdrawalDetailsForUriM(url.absoluteString) { + defaultExchangeBaseUrl = someInfo.defaultExchangeBaseUrl + possibleExchanges = someInfo.possibleExchanges + amountToTransfer = someInfo.amount ?? Amount.zero(currency: someInfo.currency) } - } else { + // TODO: amount = nil ==> show amount input } - } } } }