commit cacf407b67c6adb903c2e9e57d3037232da51a13
parent 58e1887689022a0bf836402716fb118bccabce97
Author: Marc Stibane <marc@taler.net>
Date: Sun, 30 Mar 2025 22:37:31 +0200
prepare OIM
Diffstat:
7 files changed, 591 insertions(+), 305 deletions(-)
diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj
@@ -198,6 +198,8 @@
4E847B802C9030E0003A164E /* TabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E847B7E2C9030E0003A164E /* TabBarView.swift */; };
4E847B822C9065FD003A164E /* ScopePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E847B812C9065FD003A164E /* ScopePicker.swift */; };
4E847B832C9065FD003A164E /* ScopePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E847B812C9065FD003A164E /* ScopePicker.swift */; };
+ 4E84D7AB2D902DE000D2B1CB /* OIMcurrencyScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E84D7AA2D902DE000D2B1CB /* OIMcurrencyScroller.swift */; };
+ 4E84D7AC2D902DE000D2B1CB /* OIMcurrencyScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E84D7AA2D902DE000D2B1CB /* OIMcurrencyScroller.swift */; };
4E84D7B72D96A57900D2B1CB /* LayoutThatFits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E84D7B62D96A57800D2B1CB /* LayoutThatFits.swift */; };
4E84D7B82D96A57900D2B1CB /* LayoutThatFits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E84D7B62D96A57800D2B1CB /* LayoutThatFits.swift */; };
4E87C8732A31CB7F001C6406 /* TransactionsEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E87C8722A31CB7F001C6406 /* TransactionsEmptyView.swift */; };
@@ -453,6 +455,7 @@
4E7F85162D63185E00954C30 /* Environment+EdgeInsets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Environment+EdgeInsets.swift"; sourceTree = "<group>"; };
4E847B7E2C9030E0003A164E /* TabBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = "<group>"; };
4E847B812C9065FD003A164E /* ScopePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScopePicker.swift; sourceTree = "<group>"; };
+ 4E84D7AA2D902DE000D2B1CB /* OIMcurrencyScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OIMcurrencyScroller.swift; sourceTree = "<group>"; };
4E84D7B62D96A57800D2B1CB /* LayoutThatFits.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutThatFits.swift; sourceTree = "<group>"; };
4E87C8722A31CB7F001C6406 /* TransactionsEmptyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionsEmptyView.swift; sourceTree = "<group>"; };
4E8C171C2A6509BB005B2392 /* Atkinson-Hyperlegible-Regular-102.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Atkinson-Hyperlegible-Regular-102.otf"; sourceTree = "<group>"; };
@@ -1001,6 +1004,7 @@
children = (
4EE9E1F92D7D516800365E72 /* OIMcurrency.swift */,
4EF97B972D8737250007377E /* OIMcurrencyViews.swift */,
+ 4E84D7AA2D902DE000D2B1CB /* OIMcurrencyScroller.swift */,
4EF97B9A2D8739630007377E /* OIMlineViews.swift */,
4E47924F2D660C5600749393 /* OIMView.swift */,
);
@@ -1406,6 +1410,7 @@
4E3EAE602A990778009F1BE8 /* P2pReceiveURIView.swift in Sources */,
4E3EAE612A990778009F1BE8 /* ListStyle.swift in Sources */,
4EED38552D140C1400F6C038 /* TabBarModel.swift in Sources */,
+ 4E84D7AB2D902DE000D2B1CB /* OIMcurrencyScroller.swift in Sources */,
4E3EAE622A990778009F1BE8 /* TransactionSummaryV.swift in Sources */,
4E3EAE632A990778009F1BE8 /* WalletCore.swift in Sources */,
4E3EAE642A990778009F1BE8 /* LaunchAnimationView.swift in Sources */,
@@ -1557,6 +1562,7 @@
4E3B4BC12A41E6C200CC88B8 /* P2pReceiveURIView.swift in Sources */,
4E6EDD872A363D8D0031D520 /* ListStyle.swift in Sources */,
4EED38562D140C1400F6C038 /* TabBarModel.swift in Sources */,
+ 4E84D7AC2D902DE000D2B1CB /* OIMcurrencyScroller.swift in Sources */,
4EB095582989CBFE0043A8A1 /* TransactionSummaryV.swift in Sources */,
4EB095202989CBCB0043A8A1 /* WalletCore.swift in Sources */,
4EB095672989CBFE0043A8A1 /* LaunchAnimationView.swift in Sources */,
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -20,7 +20,6 @@ struct BalancesListView: View {
@EnvironmentObject private var model: WalletModel
@EnvironmentObject private var controller: Controller
@AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
- @AppStorage("sierraLeone") var sierraLeone: Bool = false
@State private var amountToTransfer = Amount.zero(currency: EMPTYSTRING) // Update currency when used
@State private var summary = EMPTYSTRING
@@ -65,16 +64,6 @@ struct BalancesListView: View {
await refresh()
}
}
- .fullScreenCover(isPresented: $controller.oimModeActive) {
- let balance = controller.balances.first
- let currency = sierraLeone ? OIMleones : OIMeuros
-// let currency = OIMdollars
- let _ = symLog("❗️OIMView: \(currency.noteBase)")
- OIMView(scope: balance?.scopeInfo,
- amount: balance?.available,
- currency: currency,
- canEdit: true)
- }
}
}
}
diff --git a/TalerWallet1/Views/OIM/OIMView.swift b/TalerWallet1/Views/OIM/OIMView.swift
@@ -13,282 +13,290 @@ let DESCENDING = true
public let OIMBACKDARK = "tara-meinczinger-G_yCplAsnB4-unsplash"
public let OIMBACKLIGHT = "andrey-haimin-VFUTPASjhB8-unsplash"
+fileprivate func intValue(_ amount: Amount?) -> Int {
+ if let amount {
+ if !amount.isZero {
+ let value = amount.value * 100 // TODO: currency specs instead of 100
+// print("intValue: \(Int(value)) \(currency.noteBase)")
+ return Int(value)
+ }
+ }
+ return 0
+}
// MARK: -
-
-// renders all banknotes and coins in 1 horizontal scrollview
-struct OIMcurrencyScroller: View {
- let currency: OIMcurrency
- @Binding var amountVal: Int
+struct OIMamountV: View {
+ let amount: Amount?
+ let currStr: String
var body: some View {
- ScrollView(.horizontal) {
- HStack(spacing: 10) {
- ForEach(currency.bankNotes, id: \.self) { value in
- OIMnoteV(value: value, currency: currency)
- .onTapGesture {
- withAnimation(Animation.easeIn(duration: 0.25)) {
- amountVal += value
- }
- }
- }
- ForEach(currency.bankCoins, id: \.self) { value in
- OIMcoinV(value: value, currency: currency)
- .onTapGesture {
- withAnimation(Animation.easeIn(duration: 0.25)) {
- amountVal += value
- }
- }
- }
- }
- }
- .viewExtractor { view in
- if let scrollView = view as? UIScrollView {
- if #available(iOS 17.4, *) {
- scrollView.bouncesVertically = false
- } else { // Fallback on earlier versions
- scrollView.bounces = false
- }
+ HStack {
+ Spacer()
+ if let amount {
+ let amountVal = intValue(amount)
+ AmountV(currStr, cent: UInt64(amountVal), isNegative: nil)
+ .foregroundColor(WalletColors().attention) // talerColor)
+ .onTapGesture {
+ debugAnimations.toggle()
+ }
}
- }
- .padding(.bottom, 20)
- .padding(.horizontal, 8)
+ }.padding(.horizontal, UIScreen.hasFaceID ? 20 : 4)
+ .ignoresSafeArea(edges: .all)
}
}
-
// MARK: -
-// renders a spread of banknote-stacks in 2 rows
-struct NotesView2: View {
- let spread: OIMdenominations
- let currency: OIMcurrency
-
- func countVal(index: Int) -> (Int, Int)? {
- let nrOfNotes = currency.bankNotes.count - 1
- if index < nrOfNotes {
- let count = spread[index]
- if count > 0 {
- let value = currency.bankNotes[index]
- return (value, count)
- }
- }
- return nil
- }
+struct OIMbackground<Content: View>: View {
+ let amount: Amount?
+ let currStr: String
+ var content: () -> Content
+ @Environment(\.colorScheme) private var colorScheme
var body: some View {
- HStack(alignment: .top, spacing: 10) {
-// let top0: Bool
-// if let cv0 = countVal(index: 0) {
-// if cv0.1 > 1 { //
-// NoteStackV(value: cv0.0, count: cv0.1, currency: currency)
-// top0 = false
-// } else {
-// NoteStackV(value: cv0.0, count: cv0.1, currency: currency)
-// top0 = true
-// }
-// } else {
-// top0 = false
-// }
-// if let cv1 = countVal(index: 1) {
-//
-// }
+ let background = colorScheme == .dark ? OIMBACKDARK
+ : OIMBACKLIGHT
+ let backImage = Image(background)
+ .resizable()
+ let zStack = ZStack(alignment: .top) {
+ backImage
+ .ignoresSafeArea(edges: .all)
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ content()
+ OIMamountV(amount: amount, currStr: currStr)
+ }
+ if #available(iOS 16.4, *) {
+ zStack
+ .toolbar(.hidden, for: .navigationBar, .tabBar)
+ .scrollBounceBehavior(.basedOnSize, axes: .horizontal)
+ } else { // Fallback on earlier versions
+ zStack
+// .navigationBarTitle("") // must set the title before you can hide the navigation bar
+ .navigationBarHidden(true)
+// .tabBarHidden(true) // unfortunately this call doesn't exist
}
}
}
-
// MARK: -
-struct Column: Hashable {
- let topCount: Int
- let topVal: Int
- let botVal: Int
-}
-
-typealias Columns = [Column]
+struct OIMnavBack<Content: View>: View {
+ let stack: CallStack
+ let scope: ScopeInfo?
+ let currStr: String
+ @Binding var amount: Amount
+ @Binding var buttonSelected: Bool
+ var content: () -> Content
-struct ColumnView: View {
- let column: Column
- let currency: OIMcurrency
+ @Environment(\.dismiss) var dismiss // call dismiss() to pop back
var body: some View {
- VStack {
- if column.topCount > 1 {
-// let _ = print("Stack: \(column.topCount) * \(column.topVal) \(currency.noteBase)")
- OIMstackV(value: column.topVal, count: column.topCount, currency: currency)
- } else {
-// let _ = print("Single: \(column.topVal) and \(column.botVal) \(currency.noteBase)")
- OIMsingleV(value: column.topVal, currency: currency)
- if column.botVal > 0 {
- OIMsingleV(value: column.botVal, currency: currency)
+ OIMbackground(amount: amount, currStr: currStr) {
+ ZStack {
+ content()
+ VStack {
+ HStack {
+ BackButton() { dismiss() }
+ Spacer()
+ ForwardButton(enabled: !amount.isZero) {
+ buttonSelected = true
+ }.padding(.trailing, UIScreen.horzInsets)
+ }
}
+ .ignoresSafeArea(edges: .horizontal)
}
}
}
}
+// MARK: -
struct OIMView: View {
+ let stack: CallStack
let scope: ScopeInfo?
let amount: Amount?
- let currency: OIMcurrency
// let decimal: Int // 0 for ¥,HUF; 2 for $,€,£; 3 for ﷼,₯ (arabic)
- let canEdit: Bool
+ @Binding var qrButtonTapped: Bool
- @Environment(\.colorScheme) private var colorScheme
- @AppStorage("oimTwoRows") var oimTwoRows: Bool = false
-
- @State private var banknotes: Columns = []
- @State private var integerCoins: Columns = []
- @State private var fractalCoins: Columns = []
+ @AppStorage("sierraLeone") var sierraLeone: Bool = false
@State private var amountVal: Int = 0
+ @Namespace var namespace
+ @State private var tappedVal: Int = 0
+ @State private var isFlyingToDeck = false
+ @State private var shake = false
+
+ var body: some View {
+#if PRINT_CHANGES || true
+ let _ = Self._printChanges()
+#endif
+ let currency = sierraLeone ? OIMleones : OIMeuros
- var intValue: Int {
- if let amount {
- if !amount.isZero {
- let value = amount.value * 100 // TODO: currency specs instead of 100
-// print("intValue: \(Int(value)) \(currency.noteBase)")
- return Int(value)
+ let actions = HStack(spacing: 30) {
+ QRButton(isNavBarItem: false) {
+ qrButtonTapped = true
}
+ .lineLimit(5)
+ .buttonStyle(TalerButtonStyle(type: .bordered, narrow: true, aligned: .center))
+
+ SendRequestV(stack: stack.push(),
+ sendDisabled: false,
+ recvDisabled: false)
}
- return 0
- }
- func buildColumns(_ spread: OIMnotesCoins, currency: OIMcurrency) {
- var topVal = 0
- var notes: Columns = []
- var intCoins: Columns = []
- var fracCoins: Columns = []
-
- func addTopVal(andBot: Int, to columns: inout Columns) {
- if topVal > 0 {
- columns.append(Column(topCount: 1, topVal: topVal, botVal: andBot))
-// print("add \(topVal) and \(andBot) \(currency.noteBase)")
- topVal = 0
- } else if andBot > 0 {
- topVal = andBot
- }
+ OIMbackground(amount: amount, currStr: currency.noteBase) {
+ VStack {
+ Spacer()
+ OIMlineView(currency: currency,
+ amountVal: $amountVal,
+ namespace: namespace,
+ tappedVal: tappedVal,
+ isFlyingToDeck: $isFlyingToDeck,
+ shake: shake,
+ canEdit: false)
+ Spacer()
+ actions
+ }//.border(.red)
+ }.task {
+ amountVal = intValue(amount)
}
+ }
+}
+// MARK: -
+struct OIMPayView: View {
+ let scope: ScopeInfo?
+ let amount: Amount?
+// let decimal: Int // 0 for ¥,HUF; 2 for $,€,£; 3 for ﷼,₯ (arabic)
- for (index, count) in spread.0.enumerated() { // banknotes
- if count > 0 {
- let value = currency.bankNotes[index]
- if count > 1 {
- if DESCENDING {
- addTopVal(andBot: 0, to: ¬es)
- } // else fill bottom later with smaller note
- var cnt = count
- while cnt > 5 {
- notes.append(Column(topCount: 5, topVal: value, botVal: 0))
- cnt -= 5
- }
- notes.append(Column(topCount: cnt, topVal: value, botVal: 0))
-// print("notes: \(count) * \(value) \(currency.noteBase)")
- } else {
- addTopVal(andBot: value, to: ¬es)
- }
- }
+ @AppStorage("sierraLeone") var sierraLeone: Bool = false
+
+ @State private var amountVal: Int = 0
+ @Namespace var namespace
+ @State private var tappedVal: Int = 0
+ @State private var isFlyingToDeck = false
+ @State private var shake = false
+
+ var body: some View {
+#if PRINT_CHANGES || true
+ let _ = Self._printChanges()
+#endif
+ let currency = sierraLeone ? OIMleones : OIMeuros
+
+ OIMbackground(amount: amount, currStr: currency.noteBase) {
+ VStack {
+ Spacer()
+ OIMlineView(currency: currency,
+ amountVal: $amountVal,
+ namespace: namespace,
+ tappedVal: tappedVal,
+ isFlyingToDeck: $isFlyingToDeck,
+ shake: shake,
+ canEdit: true)
+ Spacer()
+ }.border(.red)
+ }.task {
+ amountVal = intValue(amount)
}
- addTopVal(andBot: 0, to: ¬es) // TODO: comment out to fill bottom later with coins
- banknotes = notes
-
- for (index, count) in spread.1.enumerated() {
- if count > 0 {
- let value = currency.bankCoins[index]
- if value > 99 { // TODO: use currency decimal
- if count > 1 {
- if DESCENDING {
- addTopVal(andBot: 0, to: &intCoins)
- } // else fill bottom later with smaller coin
- intCoins.append(Column(topCount: count, topVal: value, botVal: 0))
-// print("intCoins: \(count) * \(value) \(currency.coinBase)")
- } else {
- addTopVal(andBot: value, to: &intCoins)
- }
- } else {
- if topVal > 99 {
- addTopVal(andBot: 0, to: &intCoins) // finish last intCoin
+
+ }
+}
+// MARK: -
+struct OIMEditView: View {
+ let stack: CallStack
+ let scope: ScopeInfo?
+ @Binding var amount: Amount
+ @Binding var available: Amount
+// let decimal: Int // 0 for ¥,HUF; 2 for $,€,£; 3 for ﷼,₯ (arabic)
+ @Binding var buttonSelected: Bool
+
+ @AppStorage("sierraLeone") var sierraLeone: Bool = false
+
+ @State private var amountVal: Int = 0
+ @State private var availableVal: Int = 0
+
+ @Namespace var namespace
+ @State private var tappedVal: Int = 0
+ @State private var isFlyingToDeck = false
+ @State private var shake = false
+
+ var body: some View {
+#if PRINT_CHANGES || true
+ let _ = Self._printChanges()
+#endif
+ let currency = sierraLeone ? OIMleones : OIMeuros
+
+ OIMnavBack(stack: stack.push(),
+ scope: scope,
+ currStr: currency.noteBase,
+ amount: $amount,
+ buttonSelected: $buttonSelected
+ ) {
+ VStack {
+ Spacer()
+ OIMlineView(currency: currency,
+ amountVal: $amountVal,
+ namespace: namespace,
+ tappedVal: tappedVal,
+ isFlyingToDeck: $isFlyingToDeck,
+ shake: shake,
+ canEdit: true)
+ .onChange(of: amountVal) { newVal in
+ let currencyStr = amount.currencyStr
+ amount = Amount(currency: currencyStr, cent: UInt64(newVal))
+ availableVal = intValue(available) - intValue(amount)
}
- if count > 1 {
- if DESCENDING {
- addTopVal(andBot: 0, to: &fracCoins)
- } // else fill bottom later with smaller coin
- fracCoins.append(Column(topCount: count, topVal: value, botVal: 0))
-// print("fracCoins: \(count) * \(value) \(currency.coinBase)")
- } else {
- addTopVal(andBot: value, to: &fracCoins)
+ Spacer()
+ OIMcurrencyScroller(currency: currency,
+ availableVal: $availableVal,
+ amountVal: $amountVal,
+ namespace: namespace,
+ tappedVal: $tappedVal,
+ isFlyingToDeck: $isFlyingToDeck,
+ shake: $shake)
+ .onChange(of: available) { newVal in
+ availableVal = intValue(newVal) - intValue(amount)
}
- }
}
+ .border(.red)
+ }.task {
+ amountVal = intValue(amount)
+ availableVal = intValue(available) - intValue(amount)
}
- if topVal > 99 {
- addTopVal(andBot: 0, to: &intCoins) // finish last intCoin
- } else {
- addTopVal(andBot: 0, to: &fracCoins)
- }
- integerCoins = intCoins
- fractalCoins = fracCoins
}
+}
+// MARK: -
+struct OIMSubjectView: View {
+ let stack: CallStack
+ let scope: ScopeInfo?
+ @Binding var amount: Amount
+// let decimal: Int // 0 for ¥,HUF; 2 for $,€,£; 3 for ﷼,₯ (arabic)
+ @Binding var buttonSelected: Bool
+
+ @AppStorage("sierraLeone") var sierraLeone: Bool = false
var body: some View {
#if PRINT_CHANGES || true
let _ = Self._printChanges()
#endif
- let background = colorScheme == .dark ? OIMBACKDARK
- : OIMBACKLIGHT
- let backImage = Image(background)
- .resizable()
+ let currency = sierraLeone ? OIMleones : OIMeuros
- let vStack = VStack {
- if let amount {
+ OIMnavBack(stack: stack.push(),
+ scope: scope,
+ currStr: currency.noteBase,
+ amount: $amount,
+ buttonSelected: $buttonSelected
+ ) {
+ VStack {
+ Spacer()
HStack {
- Spacer()
- AmountV(scope, amount, isNegative: nil)
- }.padding()
- }
- Spacer()
- HStack(alignment: .top, spacing: 10) {
- if oimTwoRows {
- ForEach(banknotes, id: \.self) { column in
- ColumnView(column: column, currency: currency)
- }
- ForEach(integerCoins, id: \.self) { column in
- ColumnView(column: column, currency: currency)
- }
- HStack(alignment: .top, spacing: 10) {
- ForEach(fractalCoins, id: \.self) { column in
- ColumnView(column: column, currency: currency)
- }
- } .padding(.leading, 20)
- } else {
- OIMlineView(currency: currency, amountVal: $amountVal, canEdit: canEdit, oneLine: false)
- }
- }
- .task {
- amountVal = intValue
-// amount = 14983
+ Text("Subject")
}
+ Spacer()
+ }.border(.red)
+ }.task {
- Spacer()
- if canEdit {
- OIMcurrencyScroller(currency: currency, amountVal: $amountVal)
- .onChange(of: amountVal) { newValue in
- // print("new Amount: \(newValue)")
- let result = currency.notesCoins(newValue)
- buildColumns(result, currency: currency)
- }
- }
- }
- .frame(maxWidth: .infinity, maxHeight: .infinity)
- .background(backImage)
- .ignoresSafeArea(edges: .all)
- if #available(iOS 16.4, *) {
- vStack
- .scrollBounceBehavior(.basedOnSize, axes: .horizontal)
- } else { // Fallback on earlier versions
- vStack
}
}
}
+
+
// MARK: -
//#Preview {
// OIMView()
diff --git a/TalerWallet1/Views/OIM/OIMcurrencyScroller.swift b/TalerWallet1/Views/OIM/OIMcurrencyScroller.swift
@@ -0,0 +1,82 @@
+/*
+ * This file is part of GNU Taler, ©2022-25 Taler Systems S.A.
+ * See LICENSE.md
+ */
+/**
+ * @author Marc Stibane
+ */
+import SwiftUI
+import taler_swift
+
+// renders all banknotes and coins in 1 horizontal scrollview
+struct OIMcurrencyScroller: View {
+ let currency: OIMcurrency
+ @Binding var availableVal: Int
+ @Binding var amountVal: Int
+ let namespace: Namespace.ID
+ @Binding var tappedVal: Int
+ @Binding var isFlyingToDeck: Bool
+ @Binding var shake: Bool
+
+ func tap(value: Int, _ delay: Int = 300) {
+ isFlyingToDeck = false
+ tappedVal = value
+ let ms = debugAnimations ? 1500 : delay
+ print("tapped \(value)")
+// DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms)) {
+// print("shake start")
+// withAnimation(.shake) {
+// shake = true
+// }
+ DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms)) {
+ print("shake end")
+ withAnimation(. shake) {
+ shake = false
+ amountVal += value
+ tappedVal = 0
+ }
+ }
+// }
+ }
+ var body: some View {
+ ScrollView(.horizontal) {
+ HStack(spacing: 10) {
+ ForEach(currency.bankNotes, id: \.self) { value in
+ OIMnoteV(value: value,
+ currency: currency,
+ availableVal: availableVal,
+ canEdit: true,
+ pct: 0.0,
+ action: { tap(value: value) }
+ )
+ .matchedGeometryEffect(id: value, in: namespace, isSource: true)
+ }
+ ForEach(currency.bankCoins, id: \.self) { value in
+ OIMcoinV(value: value,
+ currency: currency,
+ availableVal: availableVal,
+ canEdit: true,
+ action: { tap(value: value) }
+ )
+ .matchedGeometryEffect(id: value, in: namespace, isSource: true)
+ }
+ }.padding(.trailing, UIScreen.hasNotch ? UIScreen.horzInsets : 0) // ensure scrolling over the FaceID notch
+ }
+ .clipped(antialiased: true)
+ .padding(.horizontal, 5)
+ .ignoresSafeArea(edges: .horizontal)
+ .viewExtractor { view in
+ if let scrollView = view as? UIScrollView {
+ if #available(iOS 17.4, *) {
+ scrollView.bouncesVertically = false
+ } else { // Fallback on earlier versions
+ scrollView.bounces = false
+ }
+ }
+ }
+ }
+}
+// MARK: -
+//#Preview {
+// OIMcurrencyScroller()
+//}
diff --git a/TalerWallet1/Views/OIM/OIMcurrencyViews.swift b/TalerWallet1/Views/OIM/OIMcurrencyViews.swift
@@ -8,20 +8,81 @@
import SwiftUI
import taler_swift
+struct OIMbuttonStyle: ButtonStyle {
+
+ public func makeBody(configuration: OIMbuttonStyle.Configuration) -> some View {
+ configuration.label
+// .foregroundColor(.white)
+// .padding(15)
+// .background(RoundedRectangle(cornerRadius: 5).fill(color))
+// .compositingGroup()
+// .shadow(color: .black, radius: 3)
+ .opacity(configuration.isPressed ? 0.5 : 1.0)
+ .scaleEffect(configuration.isPressed ? 0.9 : 1.0)
+ }
+}
+
/// renders 1 banknote from a currency with size/4
struct OIMnoteV: View {
let value: Int
let currency: OIMcurrency
+ let availableVal: Int
+ let canEdit: Bool
+ var pct: CGFloat
+ let action: () -> Void
var body: some View {
- if let name = currency.noteName(value) {
- Image(name)
- .resizable()
- .scaledToFit()
+#if PRINT_CHANGES || true
+ let _ = Self._printChanges()
+#endif
+ // Use EmptyView, because the modifier actually ignores
+ // the value passed to its body() function.
+ EmptyView().modifier(OIMmod(value: value,
+ currency: currency,
+ availableVal: availableVal,
+ canEdit: canEdit,
+ pct: pct,
+ action: action))
+ }
+}
+
+struct OIMmod: AnimatableModifier {
+ let value: Int
+ let currency: OIMcurrency
+ let availableVal: Int
+ let canEdit: Bool
+ var pct: CGFloat
+ let action: () -> Void
+
+ var animatableData: CGFloat {
+ get { pct }
+ set { pct = newValue }
+ }
+
+ func body(content: Content) -> some View {
+ let shadow = (3 - 8) * pct + 8
+ return Group {
+ if let name = currency.noteName(value) {
+ let image = Image(name)
+ .resizable()
+ .scaledToFit()
+ Group {
+ if canEdit && availableVal >= value {
+ Button(action: action) {
+ image
+ }
+ .buttonStyle(OIMbuttonStyle())
+ } else {
+ image
+ .opacity(canEdit ? 0.5 : 1.0)
+ }
+ }
.frame(width: currency.noteWidth / 4, height: currency.noteHeight / 4)
+ .shadow(radius: shadow)
.accessibilityLabel(Text("\(value)", comment: "VoiceOver"))
- } else {
- EmptyView()
+ } else {
+ EmptyView()
+ }
}
}
}
@@ -30,12 +91,26 @@ struct OIMnoteV: View {
struct OIMcoinV: View {
let value: Int
let currency: OIMcurrency
+ let availableVal: Int
+ let canEdit: Bool
+ let action: () -> Void
var body: some View {
if let name = currency.coinName(value), let size = currency.coinSize(value) {
- Image(name)
+ let image = Image(name)
.resizable()
.scaledToFit()
+ Group {
+ if canEdit && availableVal >= value {
+ Button(action: action) {
+ image
+ }
+ .buttonStyle(OIMbuttonStyle())
+ } else {
+ image
+ .opacity(canEdit ? 0.5 : 1.0)
+ }
+ }
.frame(width: size / 4, height: size / 4)
.accessibilityLabel(Text("\(value)", comment: "VoiceOver"))
} else {
@@ -48,12 +123,16 @@ struct OIMcoinV: View {
struct OIMsingleV: View {
let value: Int
let currency: OIMcurrency
+ let availableVal: Int
+ let canEdit: Bool
+ var pct: CGFloat
+ let action: () -> Void
var body: some View {
if value > currency.bankCoins[0] {
- OIMnoteV(value: value, currency: currency)
+ OIMnoteV(value: value, currency: currency, availableVal: availableVal, canEdit: canEdit, pct: pct, action: action)
} else {
- OIMcoinV(value: value, currency: currency)
+ OIMcoinV(value: value, currency: currency, availableVal: availableVal, canEdit: canEdit, action: action)
}
}
}
@@ -64,18 +143,45 @@ struct OIMnoteStackV: View {
let value: Int
let count: Int
let currency: OIMcurrency
+ let namespace: Namespace.ID
+ let tappedVal: Int
+ @Binding var isFlyingToDeck: Bool
+ let shake: Bool
+ let canEdit: Bool
+ let action: () -> Void
var body: some View {
- let offset = 20
+#if PRINT_CHANGES || true
+ let _ = Self._printChanges()
+#endif
let maxIndex = count - 1
ZStack {
ForEach(0...maxIndex, id: \.self) { index in
- OIMnoteV(value: value, currency: currency)
- .offset(x: CGFloat(offset * index), y: CGFloat(offset * index))
+ let match = tappedVal == value && index == maxIndex
+ let id = match && !isFlyingToDeck ? value : -value
+ let xOffset = CGFloat(10 * index)
+ let yOffset = CGFloat(20 * index)
+ let shakeOffset: CGSize = shake ? .random(width: 10...40, height: 5...10)
+ : .zero
+ let _ = print("id \(id), flying \(isFlyingToDeck), shaking \(shakeOffset)")
+ OIMnoteV(value: value,
+ currency: currency,
+ availableVal: value,
+ canEdit: canEdit,
+ pct: match ? 0.0 : 1.0,
+ action: action)
+ .offset(x: xOffset + shakeOffset.width, y: yOffset - shakeOffset.height)
+ .matchedGeometryEffect(id: id, in: namespace, isSource: false)
+ .onAppear {
+ print("start flying \(id), shaking \(shakeOffset)")
+ withAnimation(.fly) {
+ isFlyingToDeck = true
+ }
+ }
}
}
- .padding(.trailing, CGFloat(offset * maxIndex))
- .padding(.bottom, CGFloat(offset * maxIndex))
+ .padding(.trailing, CGFloat(10 * maxIndex))
+ .padding(.bottom, CGFloat(20 * maxIndex))
}
}
@@ -84,46 +190,43 @@ struct OIMcoinStackV: View {
let value: Int
let count: Int
let currency: OIMcurrency
+ let canEdit: Bool
+ let action: () -> Void
var body: some View {
let number = count - 1
-// let _ = print("CoinStack: \(count) * \(value) \(currency.coinBase)")
if let size = currency.coinSize(value) {
let offset = size / 16
ZStack {
ForEach(0...number, id: \.self) { index in
- OIMcoinV(value: value, currency: currency)
+ OIMcoinV(value: value, currency: currency, availableVal: value, canEdit: canEdit, action: action)
.offset(x: offset * CGFloat(index), y: offset * CGFloat(index))
}
}
-//#if DEBUG
-// .border(Color.red)
-//#endif
.padding(.trailing, offset * CGFloat(number))
.padding(.bottom, offset * CGFloat(number))
-//#if DEBUG
-// .border(Color.green)
-//#endif
}
}
}
// renders a stack of 1 denomination
-struct OIMstackV: View {
- let value: Int
- let count: Int
- let currency: OIMcurrency
-
- var body: some View {
- if value > currency.bankCoins[0] {
- OIMnoteStackV(value: value, count: count, currency: currency)
- } else {
- OIMcoinStackV(value: value, count: count, currency: currency)
- }
- }
-}
-
-
+//struct OIMstackV: View {
+// let value: Int
+// let count: Int
+// let currency: OIMcurrency
+// let canEdit: Bool
+// var pct: CGFloat
+// let action: () -> Void
+//
+// var body: some View {
+// if value > currency.bankCoins[0] {
+// OIMnoteStackV(value: value, count: count, currency: currency, canEdit: canEdit, pct: pct, action: action)
+// } else {
+// OIMcoinStackV(value: value, count: count, currency: currency, canEdit: canEdit, pct: pct, action: action)
+// }
+// }
+//}
+// MARK: -
//#Preview {
-// OIMView()
+// OIMstackV()
//}
diff --git a/TalerWallet1/Views/OIM/OIMlineViews.swift b/TalerWallet1/Views/OIM/OIMlineViews.swift
@@ -13,23 +13,37 @@ struct OIMnotesView1: View {
let spread: OIMdenominations
let currency: OIMcurrency
@Binding var amountVal: Int
+ let namespace: Namespace.ID
+ let tappedVal: Int
+ @Binding var isFlyingToDeck: Bool
+ let shake: Bool
let canEdit: Bool
var body: some View {
+#if PRINT_CHANGES || true
+ let _ = Self._printChanges()
+#endif
let nrOfNotes = currency.bankNotes.count - 1
HStack(alignment: .top, spacing: 10) {
ForEach(0...nrOfNotes, id: \.self) { index in
let count = spread[index]
- if count > 0 {
- let value = currency.bankNotes[index]
- let noteStack = OIMnoteStackV(value: value, count: count, currency: currency)
- if canEdit {
- noteStack.onTapGesture {
- withAnimation(Animation.easeIn(duration: 0.25)) {
- amountVal -= value
- }
+ let value = currency.bankNotes[index]
+ let flyingVal = tappedVal == value
+ if count > 0 || flyingVal {
+ OIMnoteStackV(value: value,
+ count: count + (flyingVal ? 1 : 0),
+ currency: currency,
+ namespace: namespace,
+ tappedVal: tappedVal,
+ isFlyingToDeck: $isFlyingToDeck,
+ shake: shake,
+ canEdit: canEdit
+ ) {
+ withAnimation(.basic) {
+ amountVal -= value // remove on button press
}
- } else { noteStack }
+ }
+ .matchedGeometryEffect(id: tappedVal, in: namespace, isSource: false)
}
}
}
@@ -42,6 +56,7 @@ struct OIMcoinsView1: View {
let spread: OIMdenominations
let currency: OIMcurrency
@Binding var amountVal: Int
+ let namespace: Namespace.ID
let canEdit: Bool
var body: some View {
@@ -51,14 +66,14 @@ struct OIMcoinsView1: View {
let count = spread[index]
if count > 0 {
let value = currency.bankCoins[index]
- let coinStack = OIMcoinStackV(value: value, count: count, currency: currency)
- if canEdit {
- coinStack.onTapGesture {
- withAnimation(Animation.easeIn(duration: 0.25)) {
- amountVal -= value
- }
+ OIMcoinStackV(value: value,
+ count: count,
+ currency: currency,
+ canEdit: canEdit) {
+ withAnimation(Animation.easeIn(duration: 0.25)) {
+ amountVal -= value
}
- } else { coinStack }
+ }
}
}
}
@@ -66,32 +81,123 @@ struct OIMcoinsView1: View {
}
// MARK: -
+fileprivate let horzSpacing: CGFloat = 20
+fileprivate let vertSpacing: CGFloat = 10
struct OIMlineView: View {
let currency: OIMcurrency
@Binding var amountVal: Int
+ let namespace: Namespace.ID
+ let tappedVal: Int
+ @Binding var isFlyingToDeck: Bool
+ let shake: Bool
let canEdit: Bool
- let oneLine: Bool
+
+ @AppStorage("oimTwoRows") var oimTwoRows: Bool = false
var body: some View {
+#if PRINT_CHANGES || true
+ let _ = Self._printChanges()
+#endif
+#if DEBUG
+ let debug = 1==0
+ let red = debug ? Color.red : Color.clear
+ let green = debug ? Color.green : Color.clear
+ let blue = debug ? Color.blue : Color.clear
+ let orange = debug ? Color.orange : Color.clear
+#endif
let result = currency.notesCoins(amountVal)
// Text("notes: \(result.0), coins: \(result.1)")
- let notes = OIMnotesView1(spread: result.0, currency: currency, amountVal: $amountVal, canEdit: canEdit)
- let coins = OIMcoinsView1(spread: result.1, currency: currency, amountVal: $amountVal, canEdit: canEdit)
- if oneLine {
- HStack {
- notes.padding(.trailing, 20)
- coins
+ let notes = OIMnotesView1(spread: result.0,
+ currency: currency,
+ amountVal: $amountVal,
+ namespace: namespace,
+ tappedVal: tappedVal,
+ isFlyingToDeck: $isFlyingToDeck,
+ shake: shake,
+ canEdit: canEdit
+ ).id("notes")
+ .matchedGeometryEffect(id: "notes", in: namespace)
+#if DEBUG
+ .border(blue)
+#endif
+ let coins = OIMcoinsView1(spread: result.1,
+ currency: currency,
+ amountVal: $amountVal,
+ namespace: namespace,
+ canEdit: canEdit
+ ).id("coins")
+ .matchedGeometryEffect(id: "coins", in: namespace)
+#if DEBUG
+ .border(red)
+#endif
+
+ let oneLine = HStack(spacing: horzSpacing) {
+ notes
+ coins
+ }
+ let twoLines = VStack(spacing: vertSpacing) {
+ notes
+ coins
+ }
+
+ let scrollView = ScrollView(.horizontal) {
+ if oimTwoRows {
+ twoLines
+#if DEBUG
+ .padding(1)
+ .border(green)
+#endif
+ } else {
+ oneLine
+#if DEBUG
+ .padding(1)
+ .border(green)
+#endif
}
- } else {
- VStack {
- notes
- coins
+ }
+ if #available(iOS 16.0, *) {
+ Group {
+ if false {
+ scrollView
+#if DEBUG
+ .padding(1)
+ .border(orange)
+#endif
+ .clipped()
+ .scrollIndicators(.visible)
+ .viewExtractor { view in
+ if let scrollView = view as? UIScrollView {
+ if #available(iOS 17.4, *) {
+ scrollView.bouncesVertically = false
+ } else { // Fallback on earlier versions
+ scrollView.bounces = false
+ }
+ }
+ }
+ } else {
+ LayoutThatFits([HStackLayout(), VStackLayout()]) {
+ notes
+#if DEBUG
+ .padding(1)
+ .border(green)
+#endif
+ coins
+ }
+#if DEBUG
+ .padding(1)
+ .border(orange)
+#endif
+ }
}
+ } else {
+ // Fallback on earlier versions
+ scrollView
}
}
}
+// MARK: -
//#Preview {
-// OIMView()
+// OIMlineView()
//}
diff --git a/TalerWallet1/Views/Sheets/Payment/PaymentView.swift b/TalerWallet1/Views/Sheets/Payment/PaymentView.swift
@@ -84,7 +84,6 @@ struct PaymentView: View, Sendable {
@EnvironmentObject private var model: WalletModel
@EnvironmentObject private var controller: Controller
@AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
- @AppStorage("sierraLeone") var sierraLeone: Bool = false
@State private var currencyInfo: CurrencyInfo = CurrencyInfo.zero(UNKNOWN)
@State var preparePayResult: PreparePayResult? = nil
@@ -298,13 +297,6 @@ struct PaymentView: View, Sendable {
}
symLog.log("Info(for: \(currency)) loaded: \(currencyInfo.name)")
}
- .overlay{
- if controller.oimSheetActive {
- let currency = sierraLeone ? OIMleones : OIMeuros
- let _ = print("❗️OIMView: \(currency.noteBase)")
- OIMView(scope: firstScope, amount: effective, currency: currency, canEdit: false)
- }
- }
} else {
LoadingView(stack: stack.push(), scopeInfo: nil, message: url.host)
.task { await viewDidLoad() }