taler-ios

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

commit 02e39080f1d77fdfa82cc3ec7c29570d83ec7311
parent 04db105e38c6120fa7445aedc93ae7134974d3d4
Author: Marc Stibane <marc@taler.net>
Date:   Sun, 12 Nov 2023 18:22:39 +0100

amountToTransfer Currency

Diffstat:
MTalerWallet1/Views/Exchange/ManualWithdraw.swift | 5++---
MTalerWallet1/Views/HelperViews/CurrencyField.swift | 98++++++++++++++++++++++++++++++++++---------------------------------------------
MTalerWallet1/Views/HelperViews/CurrencyInputView.swift | 26+++++++++++++++-----------
MTalerWallet1/Views/Peer2peer/RequestPayment.swift | 3+--
MTalerWallet1/Views/Peer2peer/SendAmount.swift | 3+--
Mtaler-swift/Sources/taler-swift/Amount.swift | 39++++++++++++++++++++++++++-------------
6 files changed, 87 insertions(+), 87 deletions(-)

diff --git a/TalerWallet1/Views/Exchange/ManualWithdraw.swift b/TalerWallet1/Views/Exchange/ManualWithdraw.swift @@ -28,13 +28,12 @@ struct ManualWithdraw: View { #endif let currency = exchange.currency ?? String(localized: "Unknown", comment: "unknown currency") let navTitle = String(localized: "NavTitle_Withdraw (currency)", defaultValue: "Withdraw \(currency)") - let currencyField = CurrencyField(amount: $amountToTransfer) // becomeFirstResponder // let agePicker = AgePicker(ageMenuList: $ageMenuList, selectedAge: $selectedAge) ScrollView { VStack { - CurrencyInputView(currencyField: currencyField, - title: String(localized: "Amount to withdraw:")) + CurrencyInputView(amount: $amountToTransfer, + title: String(localized: "Amount to withdraw:")) let someCoins = SomeCoins(details: withdrawalAmountDetails) QuiteSomeCoins(someCoins: someCoins, shouldShowFee: true, currency: currency, amountEffective: withdrawalAmountDetails?.amountEffective) diff --git a/TalerWallet1/Views/HelperViews/CurrencyField.swift b/TalerWallet1/Views/HelperViews/CurrencyField.swift @@ -22,10 +22,14 @@ import SwiftUI import UIKit import taler_swift +import SymLog @MainActor -public struct CurrencyField: View { +struct CurrencyField: View { + private let symLog = SymLogV(0) @Binding var amount: Amount // the `value´ + let currencyInfo: CurrencyInfo + private var currencyInputField: CurrencyInputField! = nil public func becomeFirstResponder() -> Void { @@ -36,34 +40,22 @@ public struct CurrencyField: View { currencyInputField.resignFirstResponder() } - private var label: String { - let mag = pow(10, formatter.maximumFractionDigits) - return formatter.string(for: Decimal(value) / mag) ?? "" - } - - public init(value: Binding<UInt64>, currency: String, formatter: NumberFormatter) { - self._value = value - self.currency = currency - self.formatter = formatter - self.currencyInputField = CurrencyInputField(value: $value, formatter: formatter) + public init(amount: Binding<Amount>, currencyInfo: CurrencyInfo) { + self._amount = amount + self.currencyInfo = currencyInfo + self.currencyInputField = CurrencyInputField(amount: self.$amount, + currencyInfo: currencyInfo) } - public init(value: Binding<UInt64>, currency: String) { - let formatter = NumberFormatter() - formatter.locale = .current - formatter.numberStyle = .currency - formatter.currencySymbol = currency - formatter.minimumFractionDigits = 2 - formatter.maximumFractionDigits = 2 - - self.init(value: value, currency: currency, formatter: formatter) - } - - public var body: some View { + var body: some View { +#if DEBUG + let _ = Self._printChanges() + let _ = symLog.vlog(amount.description) // just to get the # to compare it with .onAppear & onDisappear +#endif ZStack { // Text view to display the formatted currency // Set as priority so CurrencyInputField size doesn't affect parent - Text(label) + Text(amount.string(currencyInfo)) .layoutPriority(1) // Input text field to handle UI @@ -90,8 +82,8 @@ class NoCaretTextField: UITextField { @MainActor struct CurrencyInputField: UIViewRepresentable { - @Binding var value: UInt64 - var formatter: NumberFormatter + @Binding var amount: Amount + let currencyInfo: CurrencyInfo private let textField = NoCaretTextField(frame: .zero) func makeCoordinator() -> Coordinator { @@ -126,7 +118,7 @@ struct CurrencyInputField: UIViewRepresentable { ) // Set initial textfield text - context.coordinator.updateText(value, textField: textField) + context.coordinator.updateText(amount, textField: textField) return textField } @@ -144,30 +136,33 @@ struct CurrencyInputField: UIViewRepresentable { self.input = currencyTextField } - func setValue(_ value: UInt64, textField: UITextField) { + func setValue(_ amount: Amount, textField: UITextField) { + // Update hidden textfield text + updateText(amount, textField: textField) // Update input value - input.value = value - - // Update textfield text - updateText(value, textField: textField) +// print(input.amount.description, " := ", amount.description) + input.amount = amount } - func updateText(_ value: UInt64, textField: UITextField) { + func updateText(_ amount: Amount, textField: UITextField) { // Update field text and last valid input text - textField.text = String(value) - lastValidInput = String(value) + lastValidInput = amount.plainString(input.currencyInfo) +// print(lastValidInput) + textField.text = lastValidInput } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { // If replacement string is empty, we can assume the backspace key was hit if string.isEmpty { // Resign first responder when delete is hit when value is 0 - if input.value == 0 { + if input.amount.isZero { textField.resignFirstResponder() + } else { + // Remove trailing digit: divide value by 10 + let amount = input.amount.copy() + amount.removeDigit(input.currencyInfo) + setValue(amount, textField: textField) } - - // Remove trailing digit - setValue(UInt64(input.value / 10), textField: textField) } return true } @@ -189,7 +184,7 @@ struct CurrencyInputField: UIViewRepresentable { } // Find new character and try to get an Int value from it - guard let char = char, let digit = Int(String(char)) else { + guard let char, let digit = UInt8(String(char)), digit <= 9 else { // New character could not be converted to Int // Revert to last valid text textField.text = lastValidInput @@ -197,27 +192,18 @@ struct CurrencyInputField: UIViewRepresentable { } // Multiply by 10 to shift numbers one position to the left, revert if an overflow occurs - let (multValue, multOverflow) = input.value.multipliedReportingOverflow(by: 10) - if multOverflow { - textField.text = lastValidInput - return - } - // Add the new trailing digit, revert if an overflow occurs - let (addValue, addOverflow) = multValue.addingReportingOverflow(UInt64(digit)) - if addOverflow { - textField.text = lastValidInput - return - } + let amount = input.amount.copy() + amount.addDigit(digit, currencyInfo: input.currencyInfo) // If new value has more digits than allowed by formatter, revert - if input.formatter.maximumFractionDigits + input.formatter.maximumIntegerDigits < String(addValue).count { - textField.text = lastValidInput - return - } +// if input.formatter.maximumFractionDigits + input.formatter.maximumIntegerDigits < String(addValue).count { +// textField.text = lastValidInput +// return +// } // Update new value - setValue(addValue, textField: textField) + setValue(amount, textField: textField) } } } diff --git a/TalerWallet1/Views/HelperViews/CurrencyInputView.swift b/TalerWallet1/Views/HelperViews/CurrencyInputView.swift @@ -3,13 +3,18 @@ * See LICENSE.md */ import SwiftUI +import taler_swift struct CurrencyInputView: View { - let currencyField: CurrencyField + @Binding var amount: Amount // the `value´ let title: String + @EnvironmentObject private var controller: Controller @State var hasBeenShown = false + var body: some View { + let currencyInfo = controller.info(for: amount.currencyStr, controller.currencyTicker) + let currencyField = CurrencyField(amount: $amount, currencyInfo: currencyInfo) VStack (alignment: .leading) { Text(title) // .padding(.top) @@ -22,9 +27,9 @@ struct CurrencyInputView: View { .textFieldStyle(.roundedBorder) }.onAppear { // make CurrencyField show the keyboard after 0.4 seconds if hasBeenShown { - print("❗️Yikes: CurrencyInputView hasBeenShown") +// print("❗️Yikes: CurrencyInputView hasBeenShown") } else { - print("❗️Yikes: First CurrencyInputView❗️") +// print("❗️Yikes: First CurrencyInputView❗️") DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { hasBeenShown = true currencyField.becomeFirstResponder() @@ -37,20 +42,19 @@ struct CurrencyInputView: View { } // MARK: - #if DEBUG -fileprivate struct BindingViewContainer : View { - @State var centsToTransfer: UInt64 = 0 +struct CurrencyInputView_Previews: PreviewProvider { + struct StateContainer : View { + @State var amountToTransfer = Amount(currency: LONGCURRENCY, cent: 0) var body: some View { - let currencyField = CurrencyField(value: $centsToTransfer, currency: LONGCURRENCY) - CurrencyInputView(currencyField: currencyField, - title: "Amount to withdraw:") + CurrencyInputView(amount: $amountToTransfer, + title: "Amount to withdraw:") } -} + } -struct CurrencyInputView_Previews: PreviewProvider { static var previews: some View { List { - BindingViewContainer() + StateContainer() } } } diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift b/TalerWallet1/Views/Peer2peer/RequestPayment.swift @@ -26,10 +26,9 @@ struct RequestPayment: View { #endif let currency = amountToTransfer.currencyStr let navTitle = String(localized: "Request Money", comment: "Dialog Title") - let currencyField = CurrencyField(amount: $amountToTransfer) ScrollView { VStack { - CurrencyInputView(currencyField: currencyField, + CurrencyInputView(amount: $amountToTransfer, title: String(localized: "Amount to request:")) let someCoins = SomeCoins(details: peerPullCheck) diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift b/TalerWallet1/Views/Peer2peer/SendAmount.swift @@ -38,7 +38,6 @@ struct SendAmount: View { #endif let currency = amountAvailable.currencyStr let navTitle = String(localized: "Send \(currency)", comment: "Send currency, Dialog Title") - let currencyField = CurrencyField(amount: $amountToTransfer) let fee = fee(ppCheck: peerPushCheck) ScrollView { VStack(alignment: .trailing) { @@ -46,7 +45,7 @@ struct SendAmount: View { Text("Available: \(available)") .accessibilityFont(.title3) .padding(.bottom, 2) - CurrencyInputView(currencyField: currencyField, + CurrencyInputView(amount: $amountToTransfer, title: String(localized: "Amount to send:")) Text("+ \(fee) payment fee") .accessibilityFont(.body) diff --git a/taler-swift/Sources/taler-swift/Amount.swift b/taler-swift/Sources/taler-swift/Amount.swift @@ -247,7 +247,7 @@ public final class Amount: Codable, Hashable, @unchecked Sendable, CustomStringC /// Copies an amount. /// - Returns: A copy of the amount. - func copy() -> Amount { + public func copy() -> Amount { Amount(currency: currency, integer: integer, fraction: fraction) } @@ -283,36 +283,49 @@ public final class Amount: Codable, Hashable, @unchecked Sendable, CustomStringC /// Divides by ten public func shiftRight() { - var remainder = integer % 10 + var remainder = UInt32(integer % 10) self.integer = integer / 10 - let fractionalBase64 = UInt64(fractionalBase()) - remainder = (remainder * fractionalBase64) + UInt64(fraction) - self.fraction = UInt32(remainder / 10) + remainder = remainder * fractionalBase() + fraction + self.fraction = remainder / 10 } /// Multiplies by ten, then adds digit public func shiftLeft(add digit: UInt8, _ inputDigits: UInt) { - let mask = fractionalBase(Self.fractionalBaseDigits - inputDigits) - let shiftedInt = integer * 10 - + UInt64(fraction / (fractionalBase() / 10)) + // how many digits to shift right (e.g. inputD=2 ==> shift:=6) + let shift = Self.fractionalBaseDigits - inputDigits + // mask to zero out fractions smaller than inputDigits + let shiftMask = fractionalBase(shift) + + let carryMask = fractionalBase(Self.fractionalBaseDigits - 1) + // get biggest fractional digit + let carry = fraction / carryMask + var remainder = fraction % carryMask +// print("fraction: \(fraction) = \(carry) + \(remainder)") + let shiftedInt = integer * 10 + UInt64(carry) if shiftedInt < Self.maxValue { self.integer = shiftedInt - let remainder = (fraction % mask) * 10 + UInt32(digit) - self.fraction = remainder * fractionalBase(inputDigits) +// print("remainder: \(remainder) / shiftMask \(shiftMask) = \(remainder / shiftMask)") + remainder = (remainder / shiftMask) * 10 } else { // will get too big // Just swap the last significant digit for the one the user typed last - let remainder = (fraction % (mask / 10)) * 10 + UInt32(digit) - self.fraction = remainder * fractionalBase(inputDigits) + if shiftMask >= 10 { + remainder = (remainder / (shiftMask / 10)) * 10 + } else { + remainder = (remainder / 10) * 10 + } } + let sum = remainder + UInt32(digit) + self.fraction = sum * shiftMask +// print("(remainder: \(remainder) + \(digit)) * base(shift) \(shiftMask) = fraction \(fraction)") } /// Sets all fractional digits after inputDigits to 0 public func mask(_ inputDigits: UInt) { let mask = fractionalBase(Self.fractionalBaseDigits - inputDigits) let remainder = fraction % mask - self.fraction = remainder * fractionalBase(inputDigits) + self.fraction -= remainder } /// Adds two amounts together.