diff options
author | Marc Stibane <marc@taler.net> | 2023-11-18 19:24:55 +0100 |
---|---|---|
committer | Marc Stibane <marc@taler.net> | 2023-11-18 19:26:04 +0100 |
commit | fbf878e49ff96b939322323b1fd6b3200c714f69 (patch) | |
tree | ec1b5a045990b92d416e88a748c6a8ad67da4579 | |
parent | 5d877d3c8824d7f769f2b37aa50393cdba967dbd (diff) | |
download | taler-ios-fbf878e49ff96b939322323b1fd6b3200c714f69.tar.gz taler-ios-fbf878e49ff96b939322323b1fd6b3200c714f69.tar.bz2 taler-ios-fbf878e49ff96b939322323b1fd6b3200c714f69.zip |
ShortcutButton
-rw-r--r-- | TalerWallet1/Views/Exchange/ManualWithdraw.swift | 3 | ||||
-rw-r--r-- | TalerWallet1/Views/HelperViews/CurrencyField.swift | 70 | ||||
-rw-r--r-- | TalerWallet1/Views/HelperViews/CurrencyInputView.swift | 109 | ||||
-rw-r--r-- | TalerWallet1/Views/Peer2peer/RequestPayment.swift | 30 | ||||
-rw-r--r-- | TalerWallet1/Views/Peer2peer/SendAmount.swift | 4 | ||||
-rw-r--r-- | TalerWallet1/Views/Peer2peer/SendPurpose.swift | 16 |
6 files changed, 166 insertions, 66 deletions
diff --git a/TalerWallet1/Views/Exchange/ManualWithdraw.swift b/TalerWallet1/Views/Exchange/ManualWithdraw.swift index e371df4..6b17785 100644 --- a/TalerWallet1/Views/Exchange/ManualWithdraw.swift +++ b/TalerWallet1/Views/Exchange/ManualWithdraw.swift @@ -34,8 +34,7 @@ struct ManualWithdraw: View { VStack { CurrencyInputView(amount: $amountToTransfer, title: iconOnly ? String(localized: "How much:") - : String(localized: "Amount to withdraw:"), - shortcutLabel: String(localized: "Withdraw", comment: "VoiceOver: Withdraw $50,$25,$10,$5 shortcut buttons")) + : String(localized: "Amount to withdraw:")) let someCoins = SomeCoins(details: withdrawalAmountDetails) QuiteSomeCoins(someCoins: someCoins, shouldShowFee: true, // TODO: set to false if we never charge withdrawal fees diff --git a/TalerWallet1/Views/HelperViews/CurrencyField.swift b/TalerWallet1/Views/HelperViews/CurrencyField.swift index 784c9dd..7b74114 100644 --- a/TalerWallet1/Views/HelperViews/CurrencyField.swift +++ b/TalerWallet1/Views/HelperViews/CurrencyField.swift @@ -30,25 +30,25 @@ struct CurrencyField: View { @Binding var amount: Amount // the `value´ let currencyInfo: CurrencyInfo - private var currencyInputField: CurrencyInputField! = nil + private var currencyFieldRepresentable: CurrencyTextfieldRepresentable! = nil - public func becomeFirstResponder() -> Void { - currencyInputField.becomeFirstResponder() + public func becomeFirstResponder() -> Bool { + currencyFieldRepresentable.becomeFirstResponder() } public func resignFirstResponder() -> Void { - currencyInputField.resignFirstResponder() + currencyFieldRepresentable.resignFirstResponder() } func updateText(amount: Amount) { - currencyInputField.updateText(amount: amount) + currencyFieldRepresentable.updateText(amount: amount) } public init(amount: Binding<Amount>, currencyInfo: CurrencyInfo) { self._amount = amount self.currencyInfo = currencyInfo - self.currencyInputField = CurrencyInputField(amount: self.$amount, - currencyInfo: currencyInfo) + self.currencyFieldRepresentable = CurrencyTextfieldRepresentable(amount: self.$amount, + currencyInfo: currencyInfo) } var body: some View { @@ -61,11 +61,13 @@ struct CurrencyField: View { // Set as priority so CurrencyInputField size doesn't affect parent Text(amount.string(currencyInfo)) .layoutPriority(1) + // make the textfield use the whole width for tapping inside to become active + .frame(maxWidth: .infinity, alignment: .trailing) +// .background(.clear) this is not the problem of the white corners // Input text field to handle UI - currencyInputField - .accessibilityHidden(true) -// .textFieldStyle(.roundedBorder) + currencyFieldRepresentable +// .accessibilityHidden(true) } } } @@ -86,7 +88,7 @@ class NoCaretTextField: UITextField { } @MainActor -struct CurrencyInputField: UIViewRepresentable { +struct CurrencyTextfieldRepresentable: UIViewRepresentable { @Binding var amount: Amount let currencyInfo: CurrencyInfo @@ -96,12 +98,13 @@ struct CurrencyInputField: UIViewRepresentable { Coordinator(self) } - @MainActor public func becomeFirstResponder() -> Void { + @MainActor public func becomeFirstResponder() -> Bool { textField.becomeFirstResponder() } @MainActor public func resignFirstResponder() -> Void { textField.resignFirstResponder() + Self.endEditing() } func updateText(amount: Amount) { @@ -113,17 +116,29 @@ struct CurrencyInputField: UIViewRepresentable { } func makeUIView(context: Context) -> NoCaretTextField { + textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + // Assign delegate textField.delegate = context.coordinator // Set keyboard type textField.keyboardType = .numberPad - // Make visual components invisible + // Make visual components invisible... textField.tintColor = .clear textField.textColor = .clear textField.backgroundColor = .clear + // ... except for the bezel around the textfield + textField.borderStyle = .roundedRect +// textField.textFieldStyle(.roundedBorder) +#if DEBUG + // Debugging: add a red border around the textfield + let myColor = UIColor(red: 0.9, green: 0.1, blue:0, alpha: 1.0) + textField.layer.masksToBounds = true + textField.layer.borderColor = myColor.cgColor + // textField.layer.borderWidth = 2.0 // <- uncomment to show the border +#endif // Add editingChanged event handler textField.addTarget( context.coordinator, @@ -141,13 +156,13 @@ struct CurrencyInputField: UIViewRepresentable { class Coordinator: NSObject, UITextFieldDelegate { // Reference to currency input field - private var input: CurrencyInputField + private var textfieldRepresentable: CurrencyTextfieldRepresentable // Last valid text input string to be displayed private var lastValidInput: String? = "" - init(_ currencyTextField: CurrencyInputField) { - self.input = currencyTextField + init(_ representable: CurrencyTextfieldRepresentable) { + self.textfieldRepresentable = representable } func setValue(_ amount: Amount, textField: UITextField) { @@ -155,12 +170,12 @@ struct CurrencyInputField: UIViewRepresentable { updateText(amount, textField: textField) // Update input value // print(input.amount.description, " := ", amount.description) - input.amount = amount + textfieldRepresentable.amount = amount } func updateText(_ amount: Amount, textField: UITextField) { // Update field text and last valid input text - lastValidInput = amount.plainString(input.currencyInfo) + lastValidInput = amount.plainString(textfieldRepresentable.currencyInfo) // print("lastValidInput: `\(lastValidInput)´") textField.text = lastValidInput let endPosition = textField.endOfDocument @@ -171,18 +186,27 @@ struct CurrencyInputField: UIViewRepresentable { // 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.amount.isZero { + if textfieldRepresentable.amount.isZero { textField.resignFirstResponder() +// Self.endEditing() } else { // Remove trailing digit: divide value by 10 - let amount = input.amount.copy() - amount.removeDigit(input.currencyInfo) + let amount = textfieldRepresentable.amount.copy() + amount.removeDigit(textfieldRepresentable.currencyInfo) setValue(amount, textField: textField) } } return true } + func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + return true + } + + func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + return true + } + @objc func editingChanged(textField: NoCaretTextField) { // Get a mutable copy of last text guard var oldText = lastValidInput else { @@ -209,8 +233,8 @@ struct CurrencyInputField: UIViewRepresentable { // Multiply by 10 to shift numbers one position to the left, revert if an overflow occurs // Add the new trailing digit, revert if an overflow occurs - let amount = input.amount.copy() - amount.addDigit(digit, currencyInfo: input.currencyInfo) + let amount = textfieldRepresentable.amount.copy() + amount.addDigit(digit, currencyInfo: textfieldRepresentable.currencyInfo) // If new value has more digits than allowed by formatter, revert // if input.formatter.maximumFractionDigits + input.formatter.maximumIntegerDigits < String(addValue).count { diff --git a/TalerWallet1/Views/HelperViews/CurrencyInputView.swift b/TalerWallet1/Views/HelperViews/CurrencyInputView.swift index 7d6c045..6155802 100644 --- a/TalerWallet1/Views/HelperViews/CurrencyInputView.swift +++ b/TalerWallet1/Views/HelperViews/CurrencyInputView.swift @@ -5,24 +5,60 @@ import SwiftUI import taler_swift +fileprivate let shortcuts = [5000,2500,1000,500] // TODO: adapt for ¥ + +struct ShortcutButton: View { + let shortcut: Int + let currency: String + let currencyInfo: CurrencyInfo + let currencyField: CurrencyField + let action: (Int, CurrencyField) -> Void + + func makeButton(with newShortcut: Int) -> ShortcutButton { + ShortcutButton(shortcut: newShortcut, + currency: currency, + currencyInfo: currencyInfo, + currencyField: currencyField, + action: action) + } + + var body: some View { + let shortie = Amount(currency: currency, cent: UInt64(shortcut)) // TODO: adapt for ¥ + let title = shortie.string(currencyInfo) + let shortcutLabel = String(localized: "Shortcut", comment: "VoiceOver: $50,$25,$10,$5 shortcut buttons") + Button(action: { action(shortcut, currencyField)} ) { + Text(title) + .accessibilityFont(.callout) + }.buttonStyle(.bordered) + .accessibilityLabel("\(shortcutLabel) \(title)") + } +} +// MARK: - struct CurrencyInputView: View { @Binding var amount: Amount // the `value´ let title: String - let shortcutLabel: String @EnvironmentObject private var controller: Controller - @State var hasBeenShown = false + @State private var hasBeenShown = false + @State private var showKeyboard = 0 + @State private var useShortcut = 0 + + func action(shortcut: Int, currencyField: CurrencyField) { + useShortcut = shortcut + let shortie = Amount(currency: amount.currencyStr, cent: UInt64(shortcut)) // TODO: adapt for ¥ + currencyField.updateText(amount: shortie) + amount = shortie + currencyField.resignFirstResponder() + } var body: some View { - let shortcuts = [50,25,10,5] let currency = amount.currencyStr let currencyInfo = controller.info(for: currency, controller.currencyTicker) let currencyField = CurrencyField(amount: $amount, currencyInfo: currencyInfo) VStack (alignment: .center) { HStack { Text(title) -// .padding(.top) .accessibilityFont(.title3) .accessibilityAddTraits(.isHeader) .accessibilityRemoveTraits(.isStaticText) @@ -34,29 +70,67 @@ struct CurrencyInputView: View { .background(WalletColors().fieldBackground) .accessibilityFont(.title2) .textFieldStyle(.roundedBorder) - HStack { - ForEach(shortcuts, id: \.self) { shortcut in - let shortie = Amount(currency: currency, integer: UInt64(shortcut), fraction: 0) - let title = shortie.string(currencyInfo) - Button(title) { - currencyField.updateText(amount: shortie) - amount = shortie - }.buttonStyle(.bordered) - .accessibilityLabel("\(shortcutLabel) \(title)") + .onTapGesture { + if useShortcut != 0 { + amount = Amount.zero(currency: currency) + useShortcut = 0 + } + showKeyboard += 1 } - } + if #available(iOS 16.0, *) { + let shortcutButton = ShortcutButton(shortcut: 0, + currency: currency, + currencyInfo: currencyInfo, + currencyField: currencyField, + action: action) + ViewThatFits(in: .horizontal) { + HStack { + ForEach(shortcuts, id: \.self) { + shortcutButton.makeButton(with: $0) + .accessibilityAddTraits($0 == useShortcut ? .isSelected : []) + } + } + VStack { + let count = shortcuts.count + let half = count / 2 + HStack { + ForEach(0..<half, id: \.self) { index in + let thisShortcut = shortcuts[index] + shortcutButton.makeButton(with: thisShortcut) + .accessibilityAddTraits(thisShortcut == useShortcut ? .isSelected : []) + } + } + HStack { + ForEach(half..<count, id: \.self) { index in + let thisShortcut = shortcuts[index] + shortcutButton.makeButton(with: thisShortcut) + .accessibilityAddTraits(thisShortcut == useShortcut ? .isSelected : []) + } + } + } + VStack { + ForEach(shortcuts, id: \.self) { + shortcutButton.makeButton(with: $0) + .accessibilityAddTraits($0 == useShortcut ? .isSelected : []) + } + } + } + } // iOS 16+ only }.onAppear { // make CurrencyField show the keyboard after 0.4 seconds if hasBeenShown { // print("❗️Yikes: CurrencyInputView hasBeenShown") } else { -// print("❗️Yikes: First CurrencyInputView❗️") + print("❗️CurrencyInputView❗️") DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { hasBeenShown = true - currencyField.becomeFirstResponder() + if !currencyField.becomeFirstResponder() { + print("❗️Yikes❗️") + } } } }.onDisappear { currencyField.resignFirstResponder() + hasBeenShown = false } } } @@ -70,8 +144,7 @@ fileprivate struct Previews: PreviewProvider { var body: some View { // Preview_Content() CurrencyInputView(amount: $amountToTransfer, - title: "Amount to withdraw:", - shortcutLabel: "Withdraw") + title: "Amount to withdraw:") .environmentObject(controller) } } diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift b/TalerWallet1/Views/Peer2peer/RequestPayment.swift index 618ddee..5192731 100644 --- a/TalerWallet1/Views/Peer2peer/RequestPayment.swift +++ b/TalerWallet1/Views/Peer2peer/RequestPayment.swift @@ -31,8 +31,7 @@ struct RequestPayment: View { ScrollView { VStack { CurrencyInputView(amount: $amountToTransfer, title: iconOnly ? String(localized: "How much:") - : String(localized: "Amount to request:"), - shortcutLabel: String(localized: "Request", comment: "VoiceOver: Request $50,$25,$10,$5 shortcut buttons")) + : String(localized: "Amount to request:")) let someCoins = SomeCoins(details: peerPullCheck) QuiteSomeCoins(someCoins: someCoins, @@ -40,29 +39,24 @@ struct RequestPayment: View { currency: currency, amountEffective: peerPullCheck?.amountEffective) - HStack { - let disabled = amountToTransfer.isZero || someCoins.invalid || someCoins.tooMany + let disabled = amountToTransfer.isZero || someCoins.invalid || someCoins.tooMany - NavigationLink(destination: LazyView { - RequestPurpose(stack: stack.push(), - amountToTransfer: amountToTransfer, - fee: someCoins.fee, - summary: $summary, - expireDays: $expireDays) -// { deactivateAction() } - }) { - Text("Request \(amountToTransfer.readableDescription)") // TODO: formatter - } + NavigationLink(destination: LazyView { + RequestPurpose(stack: stack.push(), + amountToTransfer: amountToTransfer, + fee: someCoins.fee, + summary: $summary, + expireDays: $expireDays) + }) { Text("Next") } .buttonStyle(TalerButtonStyle(type: .prominent)) .disabled(disabled) - } Spacer() } } // ScrollVStack .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal) .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all)) .navigationTitle(navTitle) - .onAppear { // make CurrencyField show the keyboard + .onAppear { DebugViewC.shared.setViewID(VIEW_REQUEST_P2P, stack: stack.push()) symLog.log("❗️Yikes \(navTitle) onAppear") } @@ -70,7 +64,9 @@ struct RequestPayment: View { symLog.log("❗️Yikes \(navTitle) onDisappear") } .task(id: amountToTransfer.value) { - if !amountToTransfer.isZero { + if amountToTransfer.isZero { +// fee = EMPTYSTRING + } else { do { let ppCheck = try await model.checkPeerPullCreditM(amountToTransfer, exchangeBaseUrl: nil) peerPullCheck = ppCheck // redraw diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift b/TalerWallet1/Views/Peer2peer/SendAmount.swift index 7eef7c7..3088ff3 100644 --- a/TalerWallet1/Views/Peer2peer/SendAmount.swift +++ b/TalerWallet1/Views/Peer2peer/SendAmount.swift @@ -59,8 +59,7 @@ struct SendAmount: View { .padding(.bottom, 2) CurrencyInputView(amount: $amountToTransfer, title: iconOnly ? String(localized: "How much:") - : String(localized: "Amount to send:"), - shortcutLabel: String(localized: "Send", comment: "VoiceOver: Send $50,$25,$10,$5 shortcut buttons")) + : String(localized: "Amount to send:")) let disabled = insufficient || amountToTransfer.isZero Text(insufficient ? insufficientLabel : feeLabel) @@ -73,6 +72,7 @@ struct SendAmount: View { amountAvailable: amountAvailable, amountToTransfer: amountToTransfer, fee: fee, + currencyInfo: currencyInfo, summary: $summary, expireDays: $expireDays) }) { Text("Next") } diff --git a/TalerWallet1/Views/Peer2peer/SendPurpose.swift b/TalerWallet1/Views/Peer2peer/SendPurpose.swift index 6d7c619..ba17c92 100644 --- a/TalerWallet1/Views/Peer2peer/SendPurpose.swift +++ b/TalerWallet1/Views/Peer2peer/SendPurpose.swift @@ -13,6 +13,7 @@ struct SendPurpose: View { let amountAvailable: Amount let amountToTransfer: Amount let fee: String + let currencyInfo: CurrencyInfo @Binding var summary: String @Binding var expireDays: UInt @AppStorage("iconOnly") var iconOnly: Bool = false @@ -22,15 +23,23 @@ struct SendPurpose: View { @FocusState private var isFocused: Bool var body: some View { +#if DEBUG + let _ = Self._printChanges() + let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear +#endif + let currency = amountAvailable.currencyStr + let current = amountToTransfer.string(currencyInfo) ScrollView { VStack (spacing: 6) { - Text(amountToTransfer.readableDescription) // TODO: curreny formatter + Text(current) Text("+ \(fee) payment fee") .accessibilityFont(.body) .foregroundColor(.red) VStack(alignment: .leading, spacing: 6) { if !iconOnly { - Text("Subject:") // Purpose + Text("Enter subject:") // Purpose .accessibilityFont(.title2) + .accessibilityAddTraits(.isHeader) + .accessibilityRemoveTraits(.isStaticText) .padding(.top) } Group { if #available(iOS 16.0, *) { @@ -74,7 +83,7 @@ struct SendPurpose: View { expireDays: expireDays, transactionStarted: $transactionStarted) }) { - Text("Send \(amountToTransfer.readableDescription) now", comment: "amountToTransfer") // TODO: currency formatter + Text("Send \(current) now", comment: "amountToTransfer") // TODO: currency formatter } .buttonStyle(TalerButtonStyle(type: .prominent)) .disabled(disabled) @@ -82,7 +91,6 @@ struct SendPurpose: View { Spacer() } - .textFieldStyle(.roundedBorder) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal) } } // ScrollVStack |