commit 6ce5c6445242a3e8fed7d39d2ecdcd776445a6e2
parent 7ed1053d809cbfaefaa1bb744631b3e5ac144b99
Author: Marc Stibane <marc@taler.net>
Date: Sat, 12 Oct 2024 20:56:42 +0200
DualHeightSheet
Diffstat:
5 files changed, 188 insertions(+), 196 deletions(-)
diff --git a/TalerWallet1/Views/Actions/ActionsSheet.swift b/TalerWallet1/Views/Actions/ActionsSheet.swift
@@ -13,12 +13,11 @@ import taler_swift
/// optional: [Spend KUDOS]
/// [Send] [Request]
/// [Withdraw] [Deposit]
+/// [ Scan QR ]
struct ActionsSheet: View {
let stack: CallStack
@Binding var balances: [Balance]
- @Binding var showSpendingHint: Bool
- @Binding var amountToTransfer: Amount // does still have the wrong currency
- @Binding var summary: String
+ @Binding var qrButtonTapped: Bool
@AppStorage("minimalistic") var minimalistic: Bool = false
@AppStorage("hideSpendingHint") var hideSpendingHint: Bool = false
@@ -33,20 +32,22 @@ struct ActionsSheet: View {
}
return false
}
-
- @MainActor
- func qrButtonTapped() {
- dismissTop(stack.push())
- NotificationCenter.default.post(name: .QrScanAction,
- object: nil) // will trigger NavigationLink
+ private var canSend: Bool { // canDeposit
+ for balance in balances {
+ if !balance.available.isZero {
+ return true
+ }
+ }
+ return false
}
var body: some View {
VStack {
let width = UIScreen.screenWidth / 3
RoundedRectangle(cornerRadius: 8) // dropBar
- .foregroundColor(.primary)
+ .foregroundColor(WalletColors().gray4)
.frame(width: width + 6.5, height: 5)
+ .padding(.top, 5)
.padding(.bottom, 10)
if hasKudos && !hideSpendingHint {
@@ -57,12 +58,12 @@ struct ActionsSheet: View {
.fixedSize(horizontal: false, vertical: true) // must set this otherwise fixedInnerHeight won't work
let title = String(localized: "LinkTitle_DEMOSHOP", defaultValue: "Spend demo money")
- let action = {
+ let shopAction = {
hideSpendingHint = true
UIApplication.shared.open(URL(string:DEMOSHOP)!, options: [:])
dismissTop(stack.push())
}
- Button(action: action) {
+ Button(action: shopAction) {
HStack {
ButtonIconBadge(type: .payment, foreColor: .accentColor, done: false)
Spacer()
@@ -75,19 +76,100 @@ struct ActionsSheet: View {
.padding(.bottom, 20)
}
- QRButton(isNavBarItem: false, action: qrButtonTapped)
+ let sendDisabled = !canSend
+ SendRequestV(stack: stack.push(), sendDisabled: sendDisabled)
+ DepositWithdrawV(stack: stack.push(), sendDisabled: sendDisabled)
+ QRButton(isNavBarItem: false) {
+ qrButtonTapped = true
+ }
.lineLimit(5)
.buttonStyle(TalerButtonStyle(type: .bordered, narrow: true, aligned: .center))
+ }
+ .padding()
+ }
+}
+// MARK: -
+@available(iOS 16.4, *)
+struct DualHeightSheet: View {
+ let stack: CallStack
+ @Binding var balances: [Balance]
+ @Binding var qrButtonTapped: Bool
+
+ let logger = Logger(subsystem: "net.taler.gnu", category: "DualSheet")
+// @State private var sheetHeight: CGFloat = .zero
+ @State private var selectedDetent: PresentationDetent = .fraction(0.1)
+ @State private var detents: Set<PresentationDetent> = [.fraction(0.1)]
+// @State private var selectedDetent: PresentationDetent = .large
+// @State private var detents: Set<PresentationDetent> = [.large]
+ @State private var qrButtonTapped2: Bool = false
+ @State private var innerHeight: CGFloat = .zero
+ @State private var sheetHeight: CGFloat = .zero
- SendRequestV(stack: stack.push(),
+ func updateDetentsWithDelay() {
+ Task {
+ //(1 second = 1_000_000_000 nanoseconds)
+ try? await Task.sleep(nanoseconds: 80_000_000)
+ guard selectedDetent == .large else { return }
+ detents = [.large]
+ }
+ }
+
+ var body: some View {
+ ScrollView {
+ ActionsSheet(stack: stack.push(),
balances: $balances,
- amountToTransfer: $amountToTransfer, // does still have the wrong currency
- summary: $summary)
+ qrButtonTapped: $qrButtonTapped2)
+ .presentationDragIndicator(.hidden)
+ .presentationBackground {
+ WalletColors().gray2
+ /// overflow the bottom of the screen by a sufficient amount to fill the gap that is seen when the size changes
+ .padding(.bottom, -1000)
+ }
+ .innerHeight($innerHeight)
- DepositWithdrawV(stack: stack.push(),
- balances: $balances,
- amountToTransfer: $amountToTransfer) // does still have the wrong currency
+ .onChange(of: qrButtonTapped2) { tapped2 in
+ if tapped2 {
+ logger.log("❗️the user tapped")
+ qrButtonTapped = true // tell our caller
+ withAnimation(Animation.easeIn(duration: 0.6)) {
+ // animate this sheet to full height
+ selectedDetent = .large
+ }
+ }
+ }
+ .onChange(of: qrButtonTapped) { tapped in
+ if !tapped {
+ logger.log("❗️dismissed, cleanup")
+ sheetHeight = innerHeight
+ qrButtonTapped2 = false
+ }
+ }
+ .onChange(of: innerHeight) { newHeight in
+ logger.log("onChange❗️set sheetHeight: \(sheetHeight) -> \(newHeight)❗️")
+// withAnimation {
+ sheetHeight = newHeight
+ selectedDetent = .height(sheetHeight) // will update detents in .onChange(:)
+// }
+ }
+ .presentationDetents(detents, selection: $selectedDetent)
+ .onChange(of: selectedDetent) { newValue in
+ if newValue == .large {
+ logger.log("onChange❗️selectedDetent = .large)")
+ updateDetentsWithDelay()
+ qrButtonTapped = true // tell our caller
+ } else {
+ logger.log("onChange❗️selectedDetent = .height(\(sheetHeight))")
+ detents = [.large, .height(sheetHeight)]
+ }
+ }
+ .task {
+ logger.log("task❗️selectedDetent = .height(\(sheetHeight))")
+ selectedDetent = .height(sheetHeight) // will update detents in .onChange(:)
+ }
+// .animation(.spring(), value: selectedDetent)
}
+ .edgesIgnoringSafeArea(.all)
+ .frame(maxHeight: innerHeight)
}
}
// MARK: -
diff --git a/TalerWallet1/Views/Actions/DepositWithdrawV.swift b/TalerWallet1/Views/Actions/DepositWithdrawV.swift
@@ -12,72 +12,32 @@ import SymLog
struct DepositWithdrawV: View {
private let symLog = SymLogV(0)
let stack: CallStack
- @Binding var balances: [Balance]
-// @Binding var currencyInfo: CurrencyInfo
-// let amountAvailable: Amount?
-// let currency: String // this is the currency to be used
- @Binding var amountToTransfer: Amount // does still have the wrong currency
+ let sendDisabled: Bool
@EnvironmentObject private var model: WalletModel
@AppStorage("minimalistic") var minimalistic: Bool = false
- @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN)
- @State private var currencyName: String = UNKNOWN
- @State private var currencySymbol: String = UNKNOWN
- @State private var amountAvailable = Amount.zero(currency: EMPTYSTRING) // Update currency when used
func selectAndUpdate(_ button: Int) {
- let scope = currencyInfo.scope
- let currency = scope.currency
- amountToTransfer.setCurrency(currency)
dismissTop(stack.push())
NotificationCenter.default.post(name: button == 3 ? .DepositAction : .WithdrawAction,
object: nil) // will trigger NavigationLink
- // after user tapped a button, while navigation animation runs, contact Exchange to update Fees
- if let url = scope.url {
- Task { // runs on MainActor
- do {
- // TODO: try await model.updateExchange(scopeInfo: scope)
- try await model.updateExchange(exchangeBaseUrl: url)
- } catch { // TODO: error handling - couldn't updateExchange
- symLog.log("error: \(error)")
- }
- }
- }
- }
-
- private var canDeposit: Bool {
- for balance in balances {
- if !balance.available.isZero {
- return true
- }
- }
- return false
}
var body: some View {
- let scope = currencyInfo.scope
- let currencyName = currencyInfo.specs.name
let depositTitle = String(localized: "DepositButton_Short", defaultValue: "Deposit",
comment: "Abbreviation of button `Deposit (currency)´")
let withdrawTitle = String(localized: "WithdrawButton_Short", defaultValue: "Withdraw",
comment: "Abbreviation of button `Withdraw (currency)´")
- let twoRowButtons = TwoRowButtons(stack: stack.push(),
- sendTitle: depositTitle,
- sendType: .deposit,
- sendA11y: depositTitle,//1.tabbed(oneLine: true),
- recvTitle: withdrawTitle,
- recvType: .withdrawal,
- recvA11y: withdrawTitle,//1.tabbed(oneLine: true),
- lineLimit: 5,
- sendDisabled: !canDeposit,
- sendAction: { selectAndUpdate(3) },
- recvAction: { selectAndUpdate(4) })
- Group {
- if #available(iOS 16.0, *) {
- twoRowButtons
- } else { // view for iOS 15
- twoRowButtons
- }
- }
+ TwoRowButtons(stack: stack.push(),
+ sendTitle: depositTitle,
+ sendType: .deposit,
+ sendA11y: depositTitle,//1.tabbed(oneLine: true),
+ recvTitle: withdrawTitle,
+ recvType: .withdrawal,
+ recvA11y: withdrawTitle,//1.tabbed(oneLine: true),
+ lineLimit: 5,
+ sendDisabled: sendDisabled,
+ sendAction: { selectAndUpdate(3) },
+ recvAction: { selectAndUpdate(4) })
}
}
diff --git a/TalerWallet1/Views/Actions/SendRequestV.swift b/TalerWallet1/Views/Actions/SendRequestV.swift
@@ -12,100 +12,33 @@ import SymLog
struct SendRequestV: View {
private let symLog = SymLogV(0)
let stack: CallStack
- @Binding var balances: [Balance]
-// @Binding var currencyInfo: CurrencyInfo
-// let amountAvailable: Amount
- // let currency: String // this is the currency to be used
- @Binding var amountToTransfer: Amount // does still have the wrong currency
- @Binding var summary: String
+ let sendDisabled: Bool
@EnvironmentObject private var model: WalletModel
@EnvironmentObject private var controller: Controller
@AppStorage("minimalistic") var minimalistic: Bool = false
- @State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN)
- @State private var currencyName: String = UNKNOWN
- @State private var currencySymbol: String = UNKNOWN
- @State private var amountAvailable = Amount.zero(currency: EMPTYSTRING) // Update currency when used
func selectAndUpdate(_ button: Int) {
- let scope = currencyInfo.scope
- let currency = scope.currency
- amountToTransfer.setCurrency(currency)
dismissTop(stack.push())
NotificationCenter.default.post(name: button == 1 ? .SendAction : .RequestAction,
object: nil) // will trigger NavigationLink
- // after user tapped a button, while navigation animation runs, contact Exchange to update Fees
- if let url = scope.url {
- Task { // runs on MainActor
- do {
- // TODO: try await model.updateExchange(scopeInfo: scope)
- try await model.updateExchange(exchangeBaseUrl: url)
- } catch { // TODO: error handling - couldn't updateExchange
- symLog.log("error: \(error)")
- }
- }
- }
- }
-
- private var canSend: Bool {
- for balance in balances {
- if !balance.available.isZero {
- return true
- }
- }
- return false
}
var body: some View {
- let scope = currencyInfo.scope
- let currencyName = currencyInfo.specs.name
let sendTitle = String(localized: "SendButton_Short", defaultValue: "Send",
comment: "Abbreviation of button `Send (currency)´")
let requTitle = String(localized: "RequestButton_Short", defaultValue: "Request",
comment: "Abbreviation of button `Request (currency)´")
- let twoRowButtons = TwoRowButtons(stack: stack.push(),
- sendTitle: sendTitle,
- sendType: .peerPushDebit,
- sendA11y: sendTitle,//1.tabbed(oneLine: true),
- recvTitle: requTitle,
- recvType: .peerPullCredit,
- recvA11y: requTitle,//1.tabbed(oneLine: true),
- lineLimit: 5,
- sendDisabled: !canSend,
- sendAction: { controller.frontendState = -1; selectAndUpdate(1) },
- recvAction: { controller.frontendState = 1; selectAndUpdate(2) })
- Group {
- if #available(iOS 16.0, *) {
- twoRowButtons
- } else { // view for iOS 15
- twoRowButtons
- }
- }
- }
-}
-// MARK: -
-#if false
-struct SendRequestV_Previews: PreviewProvider {
- @MainActor
- struct StateContainer: View {
- var body: some View {
- let test = Amount(currency: TESTCURRENCY, cent: 123)
- let demo = Amount(currency: DEMOCURRENCY, cent: 123456)
-
- List {
- Section {
- SendRequestV(stack: CallStack("Preview"), currencyName: DEMOCURRENCY, amount: demo,
- sendAction: {}, recvAction: {}, rowAction: {}, balanceDest: nil)
- }
- SendRequestV(stack: CallStack("Preview"), currencyName: TESTCURRENCY, amount: test,
- sendAction: {}, recvAction: {}, rowAction: {}, balanceDest: nil)
- }
- }
- }
-
- static var previews: some View {
- StateContainer()
-// .environment(\.sizeCategory, .extraExtraLarge) Canvas Device Settings
+ TwoRowButtons(stack: stack.push(),
+ sendTitle: sendTitle,
+ sendType: .peerPushDebit,
+ sendA11y: sendTitle,//1.tabbed(oneLine: true),
+ recvTitle: requTitle,
+ recvType: .peerPullCredit,
+ recvA11y: requTitle,//1.tabbed(oneLine: true),
+ lineLimit: 5,
+ sendDisabled: sendDisabled,
+ sendAction: { controller.frontendState = -1; selectAndUpdate(1) },
+ recvAction: { controller.frontendState = 1; selectAndUpdate(2) })
}
}
-#endif
diff --git a/TalerWallet1/Views/HelperViews/View+fixedInnerHeight.swift b/TalerWallet1/Views/HelperViews/View+fixedInnerHeight.swift
@@ -31,18 +31,15 @@ struct InnerHeightPreferenceKey: PreferenceKey {
}
extension View {
- @available(iOS 16.0, *)
- func fixedInnerHeight(_ sheetHeight: Binding<CGFloat>) -> some View {
- padding()
- .overlay { // .background doesn't work
+ func innerHeight(_ height: Binding<CGFloat>) -> some View {
+ overlay { // background doesn't work for sheets
GeometryReader { proxy in
Color.clear.preference(key: InnerHeightPreferenceKey.self, value: proxy.size.height)
}
}
.onPreferenceChange(InnerHeightPreferenceKey.self) { newHeight in
// print("InnerHeightPreferenceKey \(newHeight)")
- sheetHeight.wrappedValue = newHeight
+ height.wrappedValue = newHeight
}
- .presentationDetents([.height(sheetHeight.wrappedValue)])
}
}
diff --git a/TalerWallet1/Views/Main/MainView.swift b/TalerWallet1/Views/Main/MainView.swift
@@ -40,12 +40,13 @@ struct MainView: View {
@State private var balances: [Balance] = []
@State private var selectedBalance: Balance? = nil
- @State private var sheetPresented = false
@State private var urlToOpen: URL? = nil
- @State private var showQRScanner: Bool = false
- @State private var showCameraAlert: Bool = false
+ @State private var sheetPresented = false
@State private var showActionSheet = false
@State private var showScanner = false
+// @State private var showCameraAlert: Bool = false
+ @State private var qrButtonTapped = false
+ @State private var innerHeight: CGFloat = .zero
func sheetDismissed() -> Void {
logger.info("sheet dismiss")
@@ -73,18 +74,12 @@ struct MainView: View {
showScanner: $showScanner)
.onAppear() {
#if DEBUG
- if playSoundsI != 0 && playSoundsB && !soundPlayed {
- controller.playSound(1008) // Startup chime
- }
-#endif
- soundPlayed = true
+ if playSoundsI != 0 && playSoundsB && !soundPlayed {
+ controller.playSound(1008) // Startup chime
}
- .sheet(isPresented: $showQRScanner, onDismiss: dismissingSheet) {
- let sheet = AnyView(QRSheet(stack: stack.push(".sheet"),
- balances: $balances,
- selectedBalance: $selectedBalance))
- Sheet(stack: stack.push(), sheetView: sheet)
- } // sheet
+#endif
+ soundPlayed = true
+ }
} else if controller.backendState == .error {
ErrorView(errortext: nil) // TODO: show Error View
} else {
@@ -120,6 +115,51 @@ struct MainView: View {
}.interactiveDismissDisabled()
}
}
+ .onChange(of: qrButtonTapped) { tapped in
+ if tapped {
+ let delay = if #available(iOS 16.4, *) { 0.5 } else { 0.01 }
+ withAnimation(Animation.easeOut(duration: 0.5).delay(delay)) {
+ showScanner = true // switch to qrSheet => camera on
+ } } }
+ .sheet(isPresented: $showActionSheet,
+ onDismiss: { showScanner = false; qrButtonTapped = false }
+ ) {
+ let qrSheet = AnyView(QRSheet(stack: stack.push(".sheet"),
+ balances: $balances,
+ selectedBalance: $selectedBalance))
+ if #available(iOS 16.4, *) {
+ if showScanner {
+ let _ = logger.log("❗️showScanner: .large❗️")
+ Sheet(stack: stack.push(), sheetView: qrSheet)
+ .presentationDetents([.large])
+ .transition(.opacity)
+ } else {
+ let _ = logger.log("❗️actionsSheet: small❗️")
+ DualHeightSheet(stack: stack.push(),
+ balances: $balances,
+ qrButtonTapped: $qrButtonTapped)
+ }
+ } else {
+ if showScanner {
+ Sheet(stack: stack.push(), sheetView: qrSheet)
+ .transition(.opacity)
+ } else {
+ Group {
+ Spacer()
+ ScrollView {
+ ActionsSheet(stack: stack.push(),
+ balances: $balances,
+ qrButtonTapped: $qrButtonTapped)
+ .innerHeight($innerHeight)
+// .padding()
+ }
+ .frame(maxHeight: innerHeight)
+ .edgesIgnoringSafeArea(.all)
+ }
+ .background(WalletColors().gray2)
+ }
+ }
+ }
} // body
}
// MARK: - TabBar
@@ -142,7 +182,6 @@ enum Tab: String, Hashable, CaseIterable {
switch self {
case .balances: return String(localized: "TitleBalances", defaultValue: "Balances")
case .actions: return String(localized: "TitleActions", defaultValue: "Actions")
-// case .overview: return String(localized: "TitleOverview", defaultValue: "Overview")
case .settings: return String(localized: "TitleSettings", defaultValue: "Settings")
}
}
@@ -178,6 +217,7 @@ extension MainView {
}
}
}
+
struct Content: View {
let logger: Logger
let stack: CallStack
@@ -206,11 +246,9 @@ extension MainView {
@State private var showKycAlert: Bool = false
@State private var kycURI: URL?
- @State private var sheetHeight: CGFloat = .zero
@State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING) // Update currency when used
@State private var amountLastUsed = Amount.zero(currency: EMPTYSTRING) // Update currency when used
@State private var summary: String = EMPTYSTRING
- @State private var showSpendingHint = true
@State private var myExchange: Exchange? = nil
private var openKycButton: some View {
@@ -281,8 +319,8 @@ extension MainView {
// ViewState.shared.popToRootView(nil) either do this after any of the buttons was operated, or don't do it at all
showActionSheet = true
} onActionDrag: {
-// showScanner = true
-// showActionSheet = true
+ showScanner = true
+ showActionSheet = true
}
let scope = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange,
@@ -361,7 +399,7 @@ extension MainView {
// .padding(.bottom, 10)
tabBarView
.ignoresSafeArea(.keyboard, edges: .bottom)
- }
+ } // ZStack
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onNotification(.KYCrequired) { notification in
@@ -383,30 +421,12 @@ extension MainView {
actions: { openKycButton
dismissAlertButton },
message: { Text("Tap the button to go to the KYC website.") })
-// .sheet(isPresented: $showActionSheet, onDismiss: { tabBarView.show() }) {
- .sheet(isPresented: $showActionSheet) {
- let content = VStack {
- ActionsSheet(stack: stack.push(),
- balances: $balances,
- showSpendingHint: $showSpendingHint,
- amountToTransfer: $amountToTransfer,
- summary: $summary)
- .padding(.bottom, 32)
- }
- if #available(iOS 16, *) {
- content.fixedInnerHeight($sheetHeight)
- } else {
- content
- }
- }
.onNotification(.BalanceChange) { notification in
- // reload balances on receiving BalanceChange notification ...
- logger.info(".onNotification(.BalanceChange) ==> reload")
+ logger.info(".onNotification(.BalanceChange) ==> reload balances")
shouldReloadBalances += 1
}
.onNotification(.TransactionExpired) { notification in
- // reload balances on receiving TransactionExpired notification ...
- logger.info(".onNotification(.TransactionExpired) ==> reload")
+ logger.info(".onNotification(.TransactionExpired) ==> reload balances")
shouldReloadBalances += 1
shouldReloadPending += 1
}