commit 6f0d9e24c69adbb5ac1b69021a80829f0eaf81e7
parent 5328414df3b429d8b56f102f4313c9f181c4f1c6
Author: Marc Stibane <marc@taler.net>
Date: Mon, 14 Oct 2024 23:11:35 +0200
ScopeDropDown
Diffstat:
3 files changed, 233 insertions(+), 113 deletions(-)
diff --git a/TalerWallet1/Views/Actions/Peer2peer/RequestPayment.swift b/TalerWallet1/Views/Actions/Peer2peer/RequestPayment.swift
@@ -19,44 +19,24 @@ struct RequestPayment: View {
@Binding var summary: String
@State private var balanceIndex = 0
- @State private var pickerBalances: [Balance] = []
@State private var balance: Balance? = nil // nil only when balances == []
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 count = balances.count
+ let _ = symLog.log("count = \(count)")
let scrollView = ScrollView {
- let count = pickerBalances.count
- let _ = symLog.log("count = \(count)")
if count > 0 {
- let disabled = (count == 1 || selectedBalance != nil)
- Group {
- let scopePicker = ScopePicker(value: $balanceIndex, balances: pickerBalances) { index in
+ ScopePicker(value: $balanceIndex,
+ balances: balances,
+ onlyNonZero: false) { index in
balanceIndex = index
- balance = pickerBalances[index]
- }
- let available = balance?.available
- let availableA11y = available?.formatted(isNegative: false, useISO: true, a11y: ".")
-
- let url = balance?.scopeInfo.url?.trimURL ?? EMPTYSTRING
- let a11yLabel = url + ", " + (availableA11y ?? EMPTYSTRING)
- HStack {
- Text("via", comment: "ScopePicker")
- .foregroundColor(disabled ? .secondary : .primary)
- Spacer(minLength: 2)
- scopePicker
- .disabled(disabled)
- Spacer(minLength: 2)
- }
- .accessibilityElement(children: .combine)
- .accessibilityHint(String(localized: "Choose the payment provider.", comment: "a11y"))
- .accessibilityLabel(a11yLabel)
+ balance = balances[index]
}
- .talerFont(.picker)
-// .padding(.bottom)
- .padding(.leading)
+ .padding(.horizontal)
+ .padding(.bottom, 4)
}
RequestPaymentContent(stack: stack.push(),
balance: $balance,
@@ -65,27 +45,23 @@ struct RequestPayment: View {
summary: $summary)
} // ScrollView
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
-// .scrollBounceBehavior(.basedOnSize) needs iOS 16.4
.task {
if let selectedBalance {
- pickerBalances = [selectedBalance]
balance = selectedBalance
+ balanceIndex = balances.firstIndex(of: selectedBalance) ?? 0
} else {
- pickerBalances = balances
- let count = pickerBalances.count
- if balanceIndex >= count {
- balanceIndex = 0
- }
- if count > 0 {
- balance = pickerBalances[balanceIndex]
- } else {
- balance = nil
- }
+ balanceIndex = 0
+ balance = (count > 0) ? balances[0] : nil
}
}
if #available(iOS 16.0, *) {
- scrollView.toolbar(.hidden, for: .tabBar)
+ if #available(iOS 16.4, *) {
+ scrollView.toolbar(.hidden, for: .tabBar)
+ .scrollBounceBehavior(.basedOnSize)
+ } else {
+ scrollView.toolbar(.hidden, for: .tabBar)
+ }
} else {
scrollView
}
diff --git a/TalerWallet1/Views/Actions/Peer2peer/SendAmountV.swift b/TalerWallet1/Views/Actions/Peer2peer/SendAmountV.swift
@@ -14,48 +14,38 @@ struct SendAmountV: View {
private let symLog = SymLogV(0)
let stack: CallStack
let balances: [Balance]
- @Binding var selectedBalance: Balance? // selected balance when the action button is tapped in Transactions
+ @Binding var selectedBalance: Balance?
@Binding var amountLastUsed: Amount
@Binding var summary: String
@State private var balanceIndex = 0
- @State private var nonZeroBalances: [Balance] = []
- @State private var balance: Balance? = nil // nil only when (balances / nonZeroBalances) == []
+ @State private var balance: Balance? = nil // nil only when balances == []
+
+ func firstNonZero() -> Balance? {
+ for aBalance in balances {
+ if !aBalance.available.isZero {
+ return aBalance
+ }
+ }
+ return nil
+ }
var body: some View {
#if PRINT_CHANGES
let _ = Self._printChanges()
#endif
+ let count = balances.count
+ let _ = symLog.log("count = \(count)")
let scrollView = ScrollView {
- let count = nonZeroBalances.count
- let _ = symLog.log("count = \(count)")
if count > 0 {
- let disabled = (count == 1 || selectedBalance != nil)
- Group {
- let scopePicker = ScopePicker(value: $balanceIndex, balances: nonZeroBalances) { index in
- balanceIndex = index
- balance = nonZeroBalances[index]
- }
- let available = balance?.available
- let availableA11y = available?.formatted(isNegative: false, useISO: true, a11y: ".")
-
- let url = balance?.scopeInfo.url?.trimURL ?? EMPTYSTRING
- let a11yLabel = url + ", " + (availableA11y ?? EMPTYSTRING)
- HStack {
- Text("via", comment: "ScopePicker")
- .foregroundColor(disabled ? .secondary : .primary)
- Spacer(minLength: 2)
- scopePicker
- .disabled(disabled)
- Spacer(minLength: 2)
- }
- .accessibilityElement(children: .combine)
- .accessibilityHint(String(localized: "Choose the payment provider.", comment: "a11y"))
- .accessibilityLabel(a11yLabel)
+ ScopePicker(value: $balanceIndex,
+ balances: balances,
+ onlyNonZero: true) { index in
+ balanceIndex = index
+ balance = balances[index]
}
- .talerFont(.picker)
-// .padding(.bottom)
- .padding(.leading)
+ .padding(.horizontal)
+ .padding(.bottom, 4)
}
SendAmountContent(stack: stack.push(),
balance: $balance,
@@ -64,27 +54,32 @@ struct SendAmountV: View {
summary: $summary)
} // ScrollView
.background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
-// .scrollBounceBehavior(.basedOnSize) needs iOS 16.4
.task {
if let selectedBalance {
- nonZeroBalances = [selectedBalance]
- balance = selectedBalance
- } else {
- nonZeroBalances = Balance.nonZeroBalances(balances)
- let count = nonZeroBalances.count
- if balanceIndex >= count {
- balanceIndex = 0
- }
- if count > 0 {
- balance = nonZeroBalances[balanceIndex]
+ if selectedBalance.available.isZero {
+ // find another balance
+ balance = firstNonZero()
} else {
- balance = nil
+ balance = selectedBalance
}
+ } else {
+ balance = firstNonZero()
+ }
+ if let balance {
+ balanceIndex = balances.firstIndex(of: balance) ?? 0
+ } else {
+ balanceIndex = 0
+ balance = (count > 0) ? balances[0] : nil
}
}
if #available(iOS 16.0, *) {
- scrollView.toolbar(.hidden, for: .tabBar)
+ if #available(iOS 16.4, *) {
+ scrollView.toolbar(.hidden, for: .tabBar)
+ .scrollBounceBehavior(.basedOnSize)
+ } else {
+ scrollView.toolbar(.hidden, for: .tabBar)
+ }
} else {
scrollView
}
@@ -92,7 +87,7 @@ struct SendAmountV: View {
}
// MARK: -
struct SendAmountContent: View {
- private let symLog = SymLogV()
+ private let symLog = SymLogV(0)
let stack: CallStack
@Binding var balance: Balance?
@Binding var balanceIndex: Int
@@ -229,17 +224,10 @@ struct SendAmountContent: View {
summary: $summary,
expireDays: $expireDays)
Group {
- HStack {
- Spacer()
- Text("available: \(availableStr)")
- .accessibilityLabel("available: \(availableA11y)")
- }.padding(.horizontal)
- let amountLabel = minimalistic ? String(localized: "Amount:")
- : String(localized: "Amount to send:")
AmountInputV(stack: stack.push(),
currencyInfo: $currencyInfo,
amountAvailable: $amountAvailable,
- amountLabel: amountLabel,
+ amountLabel: nil, // will use "Available: xxx", trailing
amountToTransfer: $amountToTransfer,
amountLastUsed: amountLastUsed,
wireFee: nil,
@@ -281,6 +269,7 @@ struct SendAmountContent: View {
symLog.log("❗️ \(navTitle) onDisappear")
}
.task(id: balanceIndex + (1000 * controller.currencyTicker)) {
+ symLog.log("❗️ task \(balanceIndex)")
if let balance {
scopeInfo = balance.scopeInfo
let currency = scopeInfo.currency
diff --git a/TalerWallet1/Views/HelperViews/ScopePicker.swift b/TalerWallet1/Views/HelperViews/ScopePicker.swift
@@ -7,38 +7,71 @@
*/
import SwiftUI
+fileprivate func formattedAmount(_ balance: Balance) -> String {
+ let amount = balance.available
+ return amount.formatted(isNegative: false, useISO: false)
+}
+
+fileprivate func urlOrCurrency(_ balance: Balance) -> String {
+ balance.scopeInfo.url?.trimURL ?? balance.scopeInfo.currency
+}
+
+fileprivate func pickerRow(_ balance: Balance) -> String {
+ String("\(urlOrCurrency(balance)):\t\(formattedAmount(balance).nbs)")
+}
+
struct ScopePicker: View {
+// private let symLog = SymLogV(0)
@Binding var value: Int
let balances: [Balance]
+ let onlyNonZero: Bool
let action: (Int) -> Void
@State private var selected = 0
- func formattedAmount(_ balance: Balance) -> String {
- let amount = balance.available
- return amount.formatted(isNegative: false, useISO: false)
- }
+ var body: some View {
+#if PRINT_CHANGES
+ let _ = Self._printChanges()
+#endif
+ let count = balances.count
+ if (count > 0) {
+ let balance = balances[selected]
+ let available = balance.available
+ let availableA11y = available.formatted(isNegative: false, useISO: true, a11y: ".")
+ let url = balance.scopeInfo.url?.trimURL ?? EMPTYSTRING
+ let a11yLabel = url + ", " + availableA11y
- func row(_ balance: Balance) -> String {
- let urlOrCurrency = balance.scopeInfo.url?.trimURL
- ?? balance.scopeInfo.currency
- return String("\(urlOrCurrency):\t\(formattedAmount(balance).nbs)")
- }
+ HStack(alignment: .top) {
+ let disabled = (count == 1)
+ Text("via", comment: "ScopePicker")
+ .accessibilityHidden(true)
+ .foregroundColor(disabled ? .secondary : .primary)
- var body: some View {
- if (balances.count > 0) {
- Group {
- Picker(EMPTYSTRING, selection: $selected) {
- ForEach(0..<balances.count, id: \.self) { index in
- Text(row(balances[index]))
- .tag(index)
+ if #available(iOS 16.0, *) {
+ ScopeDropDown(selection: $selected,
+ balances: balances,
+ onlyNonZero: onlyNonZero,
+ disabled: disabled)
+ } else {
+ Picker(EMPTYSTRING, selection: $selected) {
+ ForEach(0..<balances.count, id: \.self) { index in
+ let balance = balances[index]
+ Text(pickerRow(balance))
+ .tag(index)
+// .selectionDisabled(balance.available.isZero) needs iOS 17
+ }
}
+ .disabled(disabled)
+ .frame(maxWidth: .infinity)
+// .border(.red) // debugging
+ .pickerStyle(.menu)
+ .labelsHidden()
}
- .frame(width: .infinity)
- .pickerStyle(.menu)
- .labelsHidden()
}
- .onAppear() {
+ .talerFont(.picker)
+ .accessibilityLabel(a11yLabel)
+ .accessibilityHint(String(localized: "Choose the payment provider.", comment: "a11y"))
+ .task() {
withAnimation { selected = value }
}
.onChange(of: selected) { newValue in
@@ -48,6 +81,128 @@ struct ScopePicker: View {
}
}
// MARK: -
+@available(iOS 16.0, *)
+struct ScopeDropDown: View {
+ @Binding var selection: Int
+ let balances: [Balance]
+ let onlyNonZero: Bool
+ let disabled: Bool
+
+// var maxItemDisplayed: Int = 3
+
+ @State private var scrollPosition: Int?
+ @State private var showDropdown = false
+
+ func buttonAction() {
+ withAnimation {
+ showDropdown.toggle()
+ }
+ }
+
+ @ViewBuilder
+ func dropDownRow(_ balance: Balance) -> some View {
+ let hLayout = HStack(alignment: .top) {
+ Text(urlOrCurrency(balance))
+ Spacer()
+ Text(formattedAmount(balance).nbs)
+ }
+ let vLayout = VStack(alignment: .leading) {
+ Text(urlOrCurrency(balance))
+ HStack {
+ Spacer()
+ Text(formattedAmount(balance).nbs)
+ Spacer()
+ }
+ }
+ ViewThatFits(in: .horizontal) {
+ hLayout
+ vLayout
+ }
+ }
+
+ var body: some View {
+ let radius = 8.0
+ VStack {
+ let chevron = Image(systemName: "chevron.up")
+ let foreColor = disabled ? Color.secondary : Color.primary
+ let backColor = disabled ? WalletColors().backgroundColor : WalletColors().gray4
+ let otherBalances = balances.filter { $0 != balances[selection] }
+ let theList = LazyVStack(spacing: 0) {
+ ForEach(0..<otherBalances.count, id: \.self) { index in
+ let item = otherBalances[index]
+ let rowDisabled = onlyNonZero ? item.available.isZero : false
+ Button(action: {
+ withAnimation {
+ showDropdown.toggle()
+ selection = balances.firstIndex(of: item) ?? selection
+ }
+ }, label: {
+ HStack(alignment: .top) {
+ dropDownRow(item) // .border(.random)
+ .foregroundColor(rowDisabled ? .secondary : .primary)
+ Spacer()
+ chevron.foregroundColor(.clear)
+ .accessibilityHidden(true)
+ }
+
+
+ }) // .accessibilityElement(children: .combine)
+ .disabled(rowDisabled)
+ .padding(.horizontal, radius / 2)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ }
+ VStack {
+ // selected item
+ let balance = balances[selection]
+ Button(action: buttonAction) {
+ HStack(alignment: .firstTextBaseline) {
+ dropDownRow(balance)
+ .accessibilityAddTraits(.isSelected)
+ Spacer()
+ if !disabled {
+ chevron.rotationEffect(.degrees((showDropdown ? -180 : 0)))
+ .accessibilityHidden(true)
+ }
+ }
+ }
+ .padding(.horizontal, radius / 2)
+ .frame(maxWidth: .infinity, alignment: .leading)
+// .border(.red)
+ if (showDropdown) {
+ if #available(iOS 17.0, *) {
+// let toomany = balances.count > maxItemDisplayed
+// let scrollViewHeight = buttonHeight * CGFloat(toomany ? maxItemDisplayed
+// : balances.count)
+ ScrollView {
+ theList
+ .scrollTargetLayout()
+ }
+// .border(.red)
+ .scrollPosition(id: $scrollPosition)
+ .scrollDisabled(balances.count <= 3)
+// .frame(height: scrollViewHeight)
+ .onAppear {
+ scrollPosition = selection
+ }
+ } else {
+ // Fallback on earlier versions
+ ScrollView {
+ theList
+ }
+ }
+
+ }
+ }
+ .foregroundStyle(foreColor)
+ .background(RoundedRectangle(cornerRadius: radius).fill(backColor))
+ }
+ .frame(maxWidth: .infinity, alignment: .top)
+ .zIndex(100)
+ }
+}
+
+// MARK: -
//#Preview {
// ScopePicker()
//}