commit b3866accb807914b62975b5c8706e03d19e9c6bf
parent a8635e9b4c7413bb2cc6222fd7fe3709775ddc9d
Author: Marc Stibane <marc@taler.net>
Date: Sun, 27 Apr 2025 23:59:29 +0200
Use layout for iOS16+
Diffstat:
11 files changed, 413 insertions(+), 622 deletions(-)
diff --git a/TalerWallet1/Views/OIM/OIM15Views.swift b/TalerWallet1/Views/OIM/OIM15Views.swift
@@ -0,0 +1,190 @@
+/*
+ * This file is part of GNU Taler, ©2022-25 Taler Systems S.A.
+ * See LICENSE.md
+ */
+/**
+ * @author Marc Stibane
+ */
+import SwiftUI
+import SymLog
+//import taler_swift
+
+// These views are for iOS 15 only - remove once iOS 16 is the Minimum Deployment
+// MARK: -
+// renders a stack of (identical) banknotes with offset 10,20
+struct OIMnoteStackV: View {
+ private let symLog = SymLogV(0)
+ let stack: CallStack
+ let value: UInt64
+ let count: Int
+ let currency: OIMcurrency
+ let tappedVal: UInt64
+ @Binding var flying: UInt64
+ let canEdit: Bool
+ let action: () -> Void
+
+ @EnvironmentObject private var wrapper: NamespaceWrapper
+
+ var body: some View {
+// let _ = Self._printChanges()
+ let maxIndex = count - 1
+ ZStack {
+ ForEach(0...maxIndex, id: \.self) { index in
+ let match = tappedVal == value && index == maxIndex
+ let isFlying = flying > 0
+ let matchNotFlying = match && !isFlying
+ let isTarget = matchNotFlying // && isTop
+ let targetID = isTarget ? String(-Int(value))
+ : String(value) + "+" + String(index)
+ let xOffset = CGFloat(10 * index)
+ let yOffset = CGFloat(20 * index)
+ let _ = print("targetID \(targetID), flying \(flying)")
+ let fund = OIMfund(id: Int(value), value: value, state: .idle)
+ OIMcurrencyButton(stack: stack.push(),
+// value: value,
+ fund: fund,
+ currency: currency,
+ availableVal: value,
+ canEdit: canEdit,
+// isFlipped: false, // TODO: Flip coin
+ pct: match ? 0.0 : 1.0,
+ action: action)
+ .offset(x: xOffset, y: yOffset)
+ .matchedGeometryEffect(id: targetID, in: wrapper.namespace, isSource: false)
+ .onAppear {
+ print("start flying \(targetID)")
+ withAnimation(.fly1) {
+ flying = value // start flying
+ }
+ }
+ .zIndex(5)
+ }
+ }
+ .padding(.trailing, CGFloat(10 * maxIndex))
+ .padding(.bottom, CGFloat(20 * maxIndex))
+ }
+}
+// MARK: -
+// renders a stack of (identical) coins with offset size/16
+struct OIMcoinStackV: View {
+ let stack: CallStack
+ let value: UInt64
+ let count: Int
+ let currency: OIMcurrency
+ let tappedVal: UInt64
+ @Binding var flying: UInt64
+ let canEdit: Bool
+ let action: () -> Void
+
+ @EnvironmentObject private var wrapper: NamespaceWrapper
+
+ var body: some View {
+ let maxIndex = count - 1
+ if let size = currency.coinSize(value) {
+ let offset = size / 16
+ ZStack {
+ ForEach(0...maxIndex, id: \.self) { index in
+ let match = tappedVal == value && index == maxIndex
+ let isFlying = flying > 0
+ let matchNotFlying = match && !isFlying
+ let isTarget = matchNotFlying // && isTop
+ let targetID = isTarget ? String(-Int(value))
+ : String(value) + "+" + String(index)
+ let yOffset = offset * CGFloat(index)
+ let xOffset = yOffset / 2
+ let _ = print("targetID \(targetID), flying \(flying)")
+ let fund = OIMfund(id: Int(value), value: value, state: .idle) // TODO: Flip coin
+ OIMcurrencyButton(stack: stack.push(),
+// value: value,
+ fund: fund,
+ currency: currency,
+ availableVal: value,
+ canEdit: canEdit,
+// isFlipped: false, // TODO: Flip coin
+ pct: match ? 0.0 : 1.0,
+ action: action)
+ .offset(x: xOffset, y: yOffset)
+ .matchedGeometryEffect(id: targetID, in: wrapper.namespace, isSource: false)
+ .onAppear {
+ print("start flying \(targetID)")
+ withAnimation(.fly1) {
+ flying = value // start flying
+ }
+ }
+ .zIndex(5)
+ }
+ }
+ .padding(.trailing, offset * CGFloat(maxIndex))
+ .padding(.bottom, offset * CGFloat(maxIndex))
+ }
+ }
+}
+// MARK: -
+// renders a spread of banknote-stacks in 1 row
+struct OIMnotesView1: View {
+ let stack: CallStack
+ let spread: OIMdenominations
+ let currency: OIMcurrency
+ @Binding var amountVal: UInt64
+ let tappedVal: UInt64
+ @Binding var flying: UInt64
+ let canEdit: Bool
+
+ var body: some View {
+// let _ = Self._printChanges()
+ let nrOfNotes = currency.bankNotes.count - 1
+ HStack(alignment: .center, spacing: 10) {
+ ForEach(0...nrOfNotes, id: \.self) { index in
+ let value = currency.bankNotes[index]
+ let shouldFly = tappedVal == value
+ let count = spread[index] + (shouldFly ? 1 : 0)
+ if count > 0 {
+ OIMnoteStackV(stack: stack.push(),
+ value: value,
+ count: Int(count),
+ currency: currency,
+ tappedVal: tappedVal,
+ flying: $flying,
+ canEdit: canEdit
+ ) {
+ withAnimation(.fly1) {
+ amountVal -= value // remove on button press
+ } } }
+ } // ForEach
+ } // HStack
+ }
+}
+// MARK: -
+// renders a spread of coin-stacks in 1 row
+struct OIMcoinsView1: View {
+ let stack: CallStack
+ let spread: OIMdenominations
+ let currency: OIMcurrency
+ @Binding var amountVal: UInt64
+ let tappedVal: UInt64
+ @Binding var flying: UInt64
+ let canEdit: Bool
+
+ var body: some View {
+ let nrOfCoins = currency.bankCoins.count - 1
+ HStack(alignment: .top, spacing: 10) {
+ ForEach(0...nrOfCoins, id: \.self) { index in
+ let value = currency.bankCoins[index]
+ let shouldFly = tappedVal == value
+ let count = spread[index] + (shouldFly ? 1 : 0)
+ if count > 0 {
+ OIMcoinStackV(stack: stack.push(),
+ value: value,
+ count: Int(count),
+ currency: currency,
+ tappedVal: tappedVal,
+ flying: $flying,
+ canEdit: canEdit
+ ) {
+ withAnimation(Animation.easeIn1) {
+ amountVal -= value
+ } } }
+ } // ForEach
+ } // HStack
+ }
+}
diff --git a/TalerWallet1/Views/OIM/OIMView.swift b/TalerWallet1/Views/OIM/OIMView.swift
@@ -84,10 +84,10 @@ struct OIMView: View {
OIMbackground(amount: amount, currencyName: currency.noteBase) {
VStack {
Spacer()
- OIMlineViews(stack: stack.push(),
- amountVal: $amountVal,
- tappedVal: $tappedVal,
- canEdit: false)
+ OIMlineView(stack: stack.push(),
+ amountVal: $amountVal,
+ tappedVal: $tappedVal,
+ canEdit: false)
.matchedGeometryEffect(id: "OIMline", in: wrapper.namespace,isSource: true)
Spacer()
actions
@@ -121,10 +121,10 @@ struct OIMPayView: View {
OIMbackground(amount: amount, currencyName: currency.noteBase) {
VStack {
Spacer()
- OIMlineViews(stack: stack.push(),
- amountVal: $amountVal,
- tappedVal: $tappedVal,
- canEdit: true)
+ OIMlineView(stack: stack.push(),
+ amountVal: $amountVal,
+ tappedVal: $tappedVal,
+ canEdit: true)
.matchedGeometryEffect(id: "OIMline", in: wrapper.namespace,isSource: true)
Spacer()
}.border(.red)
@@ -176,10 +176,10 @@ struct OIMEditView: View {
ZStack { // without this, money would fly below the scroller
VStack { // even though this is the only item in the ZStack
Spacer()
- OIMlineViews(stack: stack.push(),
- amountVal: $amountVal,
- tappedVal: $tappedVal,
- canEdit: true)
+ OIMlineView(stack: stack.push(),
+ amountVal: $amountVal,
+ tappedVal: $tappedVal,
+ canEdit: true)
.matchedGeometryEffect(id: "OIMline", in: wrapper.namespace,isSource: true)
.zIndex(1) // make notes fly from topZ
.onChange(of: amountVal) { newVal in
@@ -201,6 +201,9 @@ struct OIMEditView: View {
}
}
.border(.red)
+// .if(.iOS15) { view in
+// view.ignoresSafeArea(edges: .bottom) // sadly, this does not work, because there is always a bottom bar :-(
+// }
} // ZStack
}
.environmentObject(cash)
@@ -242,7 +245,6 @@ struct OIMSubjectView: View {
}.task {
}
-
}
}
diff --git a/TalerWallet1/Views/OIM/OIMcash.swift b/TalerWallet1/Views/OIM/OIMcash.swift
@@ -10,37 +10,28 @@ import taler_swift
enum FundState: Int {
case idle
- case shaking
case flying
case flipped
}
/// data structure for a cash item on the table
public struct OIMfund: Identifiable, Equatable, Hashable {
+ public let id: Int // support multiple funds with the same value
let value: UInt64
- var fundState: FundState
- public let id: UUID // support multiple funds with the same value
+ var state: FundState
- var match: String {
- if fundState == .flying {
- return String(value)
- }
- return String("\(id)")
-// return String("-\(value)")
+ var targetID: String {
+ String(state == .flying ? -Int(value) // match sourceID
+ : id)
}
}
public typealias OIMfunds = [OIMfund]
-//public struct OIMfunds: Identifiable, Hashable {
-// var funds: [OIMfund]
-// public let id: UUID // support multiple funds with the same value
-//}
// MARK: -
class OIMcash: ObservableObject {
+ var ticker = 0 // increment each time a fund is added (or melted from others)
var currency: OIMcurrency
- @Published var shake: UInt64 = 0
@Published var funds: OIMfunds = []
-// @Published var funds = OIMfunds(funds: [], id: UUID())
@AppStorage("sierraLeone") var sierraLeone: Bool = false
@@ -55,47 +46,40 @@ class OIMcash: ObservableObject {
}
}
- func insertFund(_ item: OIMfund) {
- var didInsert = false
-// for (index, fund) in funds.funds.enumerated() {
- for (index, fund) in funds.enumerated() {
- if !didInsert {
- if fund.value < item.value {
- funds.insert(item, at: index)
-// funds.funds.insert(item, at: index)
- didInsert = true
- break
- }
- }
- }
- if !didInsert {
- funds.append(item)
-// funds.funds.append(item)
- }
+ func notes() -> OIMfunds {
+ let firstCoinVal = currency.bankCoins[0]
+ return funds.filter { $0.value > firstCoinVal }
+ }
+
+ func coins() -> OIMfunds {
+ let firstCoinVal = currency.bankCoins[0]
+ return funds.filter { $0.value <= firstCoinVal }
}
func addCash(_ value: UInt64) {
- let fund = OIMfund(value: value, fundState: .flying, id: UUID())
- insertFund(fund)
+ let fund = OIMfund(id: ticker, value: value, state: .flying)
+ ticker += 1
+ funds.append(fund)
}
- func removeCash(value: UInt64) {
- if let index = funds.lastIndex(where: { $0.value == value && $0.fundState == .flipped }) {
-// if let index = funds.funds.lastIndex(where: { $0.value == value && $0.fundState == .idle }) {
+ func removeCash(id: Int, value: UInt64) {
+ if let index = funds.firstIndex(where: { $0.id == id }) {
+ funds.remove(at: index)
+ } else if let index = funds.lastIndex(where: { $0.value == value && $0.state == .flipped }) {
funds.remove(at: index)
-// funds.funds.remove(at: index)
} else if let index = funds.firstIndex(where: { $0.value == value }) {
-// } else if let index = funds.funds.firstIndex(where: { $0.value == value }) {
funds.remove(at: index)
-// funds.funds.remove(at: index)
}
}
func updateFund(_ item: OIMfund) {
let itemID = item.id
- funds = funds.map {
-// funds.funds = funds.funds.map {
- $0.id == itemID ? item : $0
+ if let index = funds.firstIndex(where: { $0.id == itemID }) {
+ funds[index] = item
+ } else {
+ funds = funds.map {
+ $0.id == itemID ? item : $0
+ }
}
}
@@ -117,7 +101,6 @@ class OIMcash: ObservableObject {
func update(_ notesCoins: OIMdenominations, currencyNotesCoins: OIMdenominations) -> Bool {
var array = funds
-// var array = funds.funds
var changed = false
for (index, value) in currencyNotesCoins.enumerated() {
let wanted = notesCoins[index] // number of notes which should be shown
@@ -134,8 +117,8 @@ class OIMcash: ObservableObject {
count -= 1
}
while count < wanted {
-// let note = OIMcashItem(value: value, pos: -1, isFlying: false, shake: false, id: UUID())
- let note = OIMfund(value: value, fundState: .idle, id: UUID())
+ let note = OIMfund(id: ticker, value: value, state: .idle)
+ ticker += 1
print("update: add \(note.value)")
array.append(note)
changed = true
@@ -144,7 +127,6 @@ class OIMcash: ObservableObject {
}
if changed {
funds = array
-// funds.funds = array
}
return changed
}
diff --git a/TalerWallet1/Views/OIM/OIMcoinStackV.swift b/TalerWallet1/Views/OIM/OIMcoinStackV.swift
@@ -1,183 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-25 Taler Systems S.A.
- * See LICENSE.md
- */
-/**
- * @author Marc Stibane
- */
-import SwiftUI
-
-// MARK: -
-// renders a stack of (identical) banknotes with offset 10,20
-//struct OIMnoteStackV: View {
-// let stack: CallStack
-// let value: Int
-// let count: Int
-// let tappedVal: Int
-// @Binding var flying: Int
-// let canEdit: Bool
-// let action: () -> Void
-//
-// @EnvironmentObject private var cash: OIMcash
-// @EnvironmentObject private var wrapper: NamespaceWrapper
-//
-// var body: some View {
-//// let _ = Self._printChanges()
-// let maxIndex = count - 1
-// ZStack {
-// ForEach(0...maxIndex, id: \.self) { index in
-// let match = tappedVal == value && index == maxIndex
-// let isFlying = flying > 0
-// let id = match && !isFlying ? value : -value
-// let xOffset = CGFloat(10 * index)
-// let yOffset = CGFloat(20 * index)
-// let shakeOffset: CGFloat = (cash.shake != 0) && !match ? .random(in: 10...25)
-// : .zero
-// let direction = value < tappedVal
-//// let _ = print("id \(id), flying \(flying), shaking \(shakeOffset)")
-// OIMcurrencyButton(value: value,
-//// currency: currency,
-// availableVal: value,
-// canEdit: canEdit,
-// canFlip: true,
-// pct: match ? 0.0 : 1.0,
-// action: action)
-// .offset(x: xOffset + (direction ? shakeOffset : -shakeOffset),
-// y: yOffset)
-// .matchedGeometryEffect(id: id, in: wrapper.namespace, isSource: false)
-// .onAppear {
-//// print("start flying \(id), shaking \(shakeOffset)")
-// withAnimation(.fly1) {
-// flying = value // start flying
-// }
-// }
-// }
-// }
-// .padding(.trailing, CGFloat(10 * maxIndex))
-// .padding(.bottom, CGFloat(20 * maxIndex))
-// }
-//}
-// MARK: -
-// renders a stack of (identical) coins with offset size/16
-struct OIMcoinStackV1: View {
- let stack: CallStack
- let value: UInt64
- let count: Int
- let currency: OIMcurrency
- let tappedVal: UInt64
- @Binding var flying: UInt64
- let shake: Bool
- let canEdit: Bool
- let action: () -> Void
-
- @EnvironmentObject private var wrapper: NamespaceWrapper
-
- var body: some View {
- let maxIndex = count - 1
- if let size = currency.coinSize(value) {
- let offset = size / 16
- ZStack {
- ForEach(0...maxIndex, id: \.self) { index in
- let match = tappedVal == value && index == maxIndex
- let isFlying = flying > 0
- let matchNotFlying = match && !isFlying
- let isTarget = matchNotFlying // && isTop
- let targetId = isTarget ? String(value)
- : String(value) + "+" + String(index)
- let yOffset = offset * CGFloat(index)
- let xOffset = yOffset / 2
- let shakeOffset: CGFloat = shake && !match ? .random(in: 5...10)
- : .zero
- let direction = value < tappedVal
- OIMcurrencyButton(stack: stack.push(),
- value: value,
- currency: currency,
- availableVal: value,
- canEdit: canEdit,
-// isFlipped: false, // TODO: Flip coin
- pct: match ? 0.0 : 1.0,
- action: action)
- .offset(x: xOffset + (direction ? shakeOffset : -shakeOffset),
- y: yOffset)
- .matchedGeometryEffect(id: targetId, in: wrapper.namespace, isSource: false)
- .onAppear {
-// print("start flying \(id), shaking \(shakeOffset)")
- withAnimation(.fly1) {
- flying = value // start flying
- }
- }
- }
- }
- .padding(.trailing, offset * CGFloat(maxIndex))
- .padding(.bottom, offset * CGFloat(maxIndex))
- }
- }
-}
-
-// MARK: -
-// renders a stack of (identical) coins with offset size/16
-struct OIMcoinStackV2: View {
- let stack: CallStack
- let value: UInt64
- let count: Int
- let tappedVal: UInt64
- @Binding var flying: UInt64
- let canEdit: Bool
- let action: () -> Void
-
- @EnvironmentObject private var cash: OIMcash
- @EnvironmentObject private var wrapper: NamespaceWrapper
-
- var body: some View {
- let isFlying = flying > 0
- let match = tappedVal == value
- let matchNotFlying = match && !isFlying
- let maxIndex = count - 1
- let currency = cash.currency
- if let size = currency.coinSize(value) {
- let offset = size / 16
- ZStack {
- ForEach(0...maxIndex, id: \.self) { index in
- let isTop = index == maxIndex
- let isTarget = matchNotFlying && isTop
- let targetId = isTarget ? String(value)
- : String(index)
-// if value == 200 || value == 100 {
-// let _ = print("OIMcoinStackV", flying, targetId)
-// }
- let yOffset = offset * CGFloat(index)
- let xOffset = yOffset / 2
- let shakeOffset: CGFloat = (cash.shake != 0) && !match ? .random(in: 5...10)
- : .zero
- let direction = value < tappedVal
- OIMcurrencyButton(stack: stack.push(),
- value: value,
- currency: cash.currency,
- availableVal: value,
- canEdit: canEdit,
-// isFlipped: false, // TODO: Flip coin
- pct: match ? 0.0 : 1.0,
- action: action)
- .offset(x: xOffset + (direction ? shakeOffset : -shakeOffset),
- y: yOffset)
- .matchedGeometryEffect(id: targetId, in: wrapper.namespace, isSource: false)
- .onAppear {
- if isTarget {
- print(" OIMcoinStackV start flying", flying, targetId)
- withAnimation(.fly1) {
- flying = value // start flying
- }
- print(" OIMcoinStackV started flying", flying, targetId)
- }
- }
- }
- }
- .padding(.trailing, offset * CGFloat(maxIndex))
- .padding(.bottom, offset * CGFloat(maxIndex))
- }
- }
-}
-// MARK: -
-//#Preview {
-// OIMstackV()
-//}
diff --git a/TalerWallet1/Views/OIM/OIMcoinsView.swift b/TalerWallet1/Views/OIM/OIMcoinsView.swift
@@ -1,86 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-25 Taler Systems S.A.
- * See LICENSE.md
- */
-/**
- * @author Marc Stibane
- */
-import SwiftUI
-import taler_swift
-
-// MARK: -
-// renders a spread of coin-stacks in 1 row
-struct OIMcoinsView1: View {
- let stack: CallStack
- let spread: OIMdenominations
- let currency: OIMcurrency
- @Binding var amountVal: UInt64
- let tappedVal: UInt64
- @Binding var flying: UInt64
- let shake: Bool
- let canEdit: Bool
-
- var body: some View {
- let nrOfCoins = currency.bankCoins.count - 1
- HStack(alignment: .top, spacing: 10) {
- ForEach(0...nrOfCoins, id: \.self) { index in
- let value = currency.bankCoins[index]
- let shouldFly = tappedVal == value
- let count = spread[index] + (shouldFly ? 1 : 0)
- if count > 0 {
- OIMcoinStackV1(stack: stack.push(),
- value: value,
- count: Int(count),
- currency: currency,
- tappedVal: tappedVal,
- flying: $flying,
- shake: shake,
- canEdit: canEdit
- ) {
- withAnimation(Animation.easeIn1) {
- amountVal -= value
- } } }
- } // ForEach
- } // HStack
- }
-}
-// MARK: -
-// renders a spread of coin-stacks in 1 row
-struct OIMcoinsView2: View {
- let stack: CallStack
- let spread: OIMdenominations
- @Binding var amountVal: UInt64
- let tappedVal: UInt64
- @Binding var flying: UInt64
- let canEdit: Bool
-
- @EnvironmentObject private var cash: OIMcash
-
- var body: some View {
- let _ = Self._printChanges()
- let currency = cash.currency
- let nrOfCoins = currency.bankCoins.count - 1
- HStack(alignment: .top, spacing: 10) {
- ForEach(0...nrOfCoins, id: \.self) { index in
- let value = currency.bankCoins[index]
- let shouldFly = tappedVal == value
- let count = spread[index] + (shouldFly ? 1 : 0)
- if count > 0 {
- OIMcoinStackV2(stack: stack.push(),
- value: value,
- count: Int(count),
- tappedVal: tappedVal,
- flying: $flying,
- canEdit: canEdit
- ) {
- withAnimation(Animation.easeIn1) {
- amountVal -= value
- } } }
- } // ForEach
- } // HStack
- }
-}
-// MARK: -
-//#Preview {
-// OIMcoinsView()
-//}
diff --git a/TalerWallet1/Views/OIM/OIMcurrencyButton.swift b/TalerWallet1/Views/OIM/OIMcurrencyButton.swift
@@ -48,7 +48,7 @@ struct OIMmod: AnimatableModifier {
let name: String?
let availableVal: UInt64
let canEdit: Bool
-// let isFlipped: Bool
+ let isFlipped: Bool
var pct: CGFloat
let action: () -> Void
@@ -63,6 +63,8 @@ struct OIMmod: AnimatableModifier {
let image = currencyImage.image
.resizable()
.scaledToFit()
+ let oWidth = currencyImage.oWidth / 4
+ let oHeight = currencyImage.oHeight / 4
return Group {
if canEdit && availableVal >= value {
Button(action: action) {
@@ -74,10 +76,11 @@ struct OIMmod: AnimatableModifier {
.opacity(canEdit ? 0.5 : 1.0)
}
}
- .frame(width: currencyImage.oWidth / 4, height: currencyImage.oHeight / 4)
+ .frame(width: (isFlipped ? 0.7 * oWidth : oWidth),
+ height: (isFlipped ? 0.7 * oHeight : oHeight))
.shadow(radius: shadow)
-// .rotation3DEffect(.degrees(isFlipped ? 180 : 0), axis: (x: 0, y: 1, z: 0))
-// .animation(.basic1, value: isFlipped)
+ .rotation3DEffect(.degrees(isFlipped ? 135 : 0), axis: (x: 0, y: 1, z: 0))
+ .animation(.basic1, value: isFlipped)
.accessibilityLabel(Text("\(value)", comment: "VoiceOver")) // TODO: currency name
}
}
@@ -85,11 +88,10 @@ struct OIMmod: AnimatableModifier {
/// renders 1 denomination from a currency
struct OIMcurrencyButton: View {
let stack: CallStack
- let value: UInt64
+ let fund: OIMfund
let currency: OIMcurrency
let availableVal: UInt64
let canEdit: Bool
-// let isFlipped: Bool
var pct: CGFloat
let action: () -> Void
@@ -97,6 +99,7 @@ struct OIMcurrencyButton: View {
var body: some View {
let currency = cash.currency
+ let value = fund.value
let name = value > currency.bankCoins[0] ? currency.noteName(value)
: currency.coinName(value)
// Use EmptyView, because the modifier actually ignores
@@ -105,7 +108,7 @@ struct OIMcurrencyButton: View {
name: name,
availableVal: availableVal,
canEdit: canEdit,
-// isFlipped: isFlipped,
+ isFlipped: fund.state == .flipped,
pct: pct,
action: action))
}
diff --git a/TalerWallet1/Views/OIM/OIMcurrencyScroller.swift b/TalerWallet1/Views/OIM/OIMcurrencyScroller.swift
@@ -22,36 +22,33 @@ struct OIMcurrencyScroller: View {
ScrollView(.horizontal) {
HStack(alignment: .bottom, spacing: 10) {
ForEach(currency.bankNotes, id: \.self) { value in
+ let fund = OIMfund(id: -Int(value), value: value, state: .idle)
+ let sourceID = -Int(value)
OIMcurrencyButton(stack: stack.push(),
- value: value,
+ fund: fund,
currency: currency,
availableVal: availableVal,
canEdit: true,
-// isFlipped: false,
pct: 0.0,
action: { tappedVal = value }
)
-// .matchedGeometryEffect(id: value, in: wrapper.namespace, isSource: true)
- .matchedGeometryEffect(id: String(value), in: wrapper.namespace, isSource: true)
+ .matchedGeometryEffect(id: String(sourceID), in: wrapper.namespace, isSource: true)
}
ForEach(currency.bankCoins, id: \.self) { value in
+ let fund = OIMfund(id: -Int(value), value: value, state: .idle)
+ let sourceID = -Int(value)
OIMcurrencyButton(stack: stack.push(),
- value: value,
+ fund: fund,
currency: currency,
availableVal: availableVal,
canEdit: true,
-// isFlipped: false,
pct: 0.0,
action: { tappedVal = value }
)
-// .matchedGeometryEffect(id: value, in: wrapper.namespace, isSource: true)
- .matchedGeometryEffect(id: String(value), in: wrapper.namespace, isSource: true)
+ .matchedGeometryEffect(id: String(sourceID), in: wrapper.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, *) {
diff --git a/TalerWallet1/Views/OIM/OIMcurrencyViews2.swift b/TalerWallet1/Views/OIM/OIMcurrencyViews2.swift
@@ -1,71 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-25 Taler Systems S.A.
- * See LICENSE.md
- */
-/**
- * @author Marc Stibane
- */
-import SwiftUI
-import taler_swift
-
-
-// MARK: -
-// renders a stack of (identical) banknotes with offset 10,20
-struct OIMnoteStackV: View {
- let stack: CallStack
- let value: UInt64
- let count: Int
- let currency: OIMcurrency
- let tappedVal: UInt64
- @Binding var flying: UInt64
- let shake: Bool
- let canEdit: Bool
- let action: () -> Void
-
- @EnvironmentObject private var wrapper: NamespaceWrapper
-
- var body: some View {
-// let _ = Self._printChanges()
- let maxIndex = count - 1
- ZStack {
- ForEach(0...maxIndex, id: \.self) { index in
- let match = tappedVal == value && index == maxIndex
- let isFlying = flying > 0
- let matchNotFlying = match && !isFlying
- let isTarget = matchNotFlying // && isTop
- let targetId = isTarget ? String(value)
- : String(value) + "+" + String(index)
- let xOffset = CGFloat(10 * index)
- let yOffset = CGFloat(20 * index)
- let shakeOffset: CGFloat = shake && !match ? .random(in: 10...25)
- : .zero
- let direction = value < tappedVal
-// let _ = print("id \(id), flying \(flying), shaking \(shakeOffset)")
- OIMcurrencyButton(stack: stack.push(),
- value: value,
- currency: currency,
- availableVal: value,
- canEdit: canEdit,
-// isFlipped: false, // TODO: Flip coin
- pct: match ? 0.0 : 1.0,
- action: action)
- .offset(x: xOffset + (direction ? shakeOffset : -shakeOffset),
- y: yOffset)
- .matchedGeometryEffect(id: targetId, in: wrapper.namespace, isSource: false)
- .onAppear {
-// print("start flying \(id), shaking \(shakeOffset)")
- withAnimation(.fly1) {
- flying = value // start flying
- }
- }
- }
- }
- .padding(.trailing, CGFloat(10 * maxIndex))
- .padding(.bottom, CGFloat(20 * maxIndex))
- }
-}
-
-// MARK: -
-//#Preview {
-// OIMstackV()
-//}
diff --git a/TalerWallet1/Views/OIM/OIMlayout.swift b/TalerWallet1/Views/OIM/OIMlayout.swift
@@ -18,7 +18,7 @@ struct OIMfundState: LayoutValueKey {
static let defaultValue: FundState = .idle
}
-@available(iOS 16.0, *)
+@available(iOS 16.4, *)
extension View {
func oimID(_ value: Int) -> some View {
self.layoutValue(key: OIMid.self, value: value)
@@ -31,7 +31,7 @@ extension View {
}
}
-@available(iOS 16.0, *)
+@available(iOS 16.4, *)
extension LayoutSubview {
var oimID: Int { self[OIMid.self] }
var oimValue: UInt64 { self[OIMvalue.self] }
@@ -40,7 +40,7 @@ extension LayoutSubview {
// MARK: -
// renders a stack of (identical) banknotes with offset 10,20
-@available(iOS 16.0, *)
+@available(iOS 16.4, *)
struct OIMlayoutView: View {
private let symLog = SymLogV(0)
let stack: CallStack
@@ -78,7 +78,7 @@ struct OIMlayoutView: View {
cash.updateFund(fund)
}
DispatchQueue.main.async {
- withAnimation(.remove1) {
+ withAnimation(.fly1) {
symLog.log(" OIMlayoutView remove \(value)")
cash.removeCash(id: fund.id, value: fund.value)
}
@@ -111,7 +111,7 @@ struct OIMlayoutView: View {
}
}
// MARK: -
-@available(iOS 16.0, *)
+@available(iOS 16.4, *)
struct OIMlayout: Layout {
struct CacheData {
var maxHeight: CGFloat
diff --git a/TalerWallet1/Views/OIM/OIMlineView.swift b/TalerWallet1/Views/OIM/OIMlineView.swift
@@ -0,0 +1,156 @@
+/*
+ * This file is part of GNU Taler, ©2022-25 Taler Systems S.A.
+ * See LICENSE.md
+ */
+/**
+ * @author Marc Stibane
+ */
+import SwiftUI
+import taler_swift
+
+fileprivate let horzSpacing: CGFloat = 20
+fileprivate let vertSpacing: CGFloat = 10
+
+struct OIMlineView: View {
+ let stack: CallStack
+ @Binding var amountVal: UInt64
+ @Binding var tappedVal: UInt64
+ let canEdit: Bool
+
+ @EnvironmentObject private var cash: OIMcash
+ @EnvironmentObject private var wrapper: NamespaceWrapper
+ @AppStorage("oimTwoRows") var oimTwoRows: Bool = false
+
+ @State private var flying: UInt64 = 0 // needed for iOS 15
+ @State private var myTappedVal: UInt64 = 0
+
+ var body: some View {
+// let _ = Self._printChanges()
+#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 currency = cash.currency
+
+ Group {
+ if #available(iOS 16.4, *) {
+ let notes = OIMlayoutView(stack: stack.push(),
+ funds: cash.notes(),
+ amountVal: $amountVal,
+ canEdit: true)
+ let coins = OIMlayoutView(stack: stack.push(),
+ funds: cash.coins(),
+ amountVal: $amountVal,
+ canEdit: true)
+ LayoutThatFits([HStackLayout(alignment: .center), VStackLayout()]) {
+ notes
+#if DEBUG
+ .padding(1)
+ .border(green)
+#endif
+ coins
+ }
+#if DEBUG
+ .padding(1)
+ .border(orange)
+#endif
+ } else { // iOS 15
+ let result = currency.notesCoins(amountVal)
+ // Text("notes: \(result.0), coins: \(result.1)")
+ let notes = OIMnotesView1(stack: stack.push(),
+ spread: result.0,
+ currency: currency,
+ amountVal: $amountVal,
+ tappedVal: myTappedVal,
+ flying: $flying,
+ canEdit: canEdit
+ ).id("notes")
+ .matchedGeometryEffect(id: "notes", in: wrapper.namespace)
+#if DEBUG
+ .border(blue)
+#endif
+ let coins = OIMcoinsView1(stack: stack.push(),
+ spread: result.1,
+ currency: currency,
+ amountVal: $amountVal,
+ tappedVal: myTappedVal,
+ flying: $flying,
+ canEdit: canEdit
+ ).id("coins")
+ .matchedGeometryEffect(id: "coins", in: wrapper.namespace)
+#if DEBUG
+ .border(red)
+#endif
+
+ ScrollView(.horizontal) {
+ if oimTwoRows {
+ VStack(spacing: vertSpacing) {
+ notes
+ coins
+ }
+#if DEBUG
+ .padding(1)
+ .border(green)
+#endif
+ } else {
+ HStack(alignment: .center, spacing: horzSpacing) {
+ notes
+ coins
+ }
+#if DEBUG
+ .padding(1)
+ .border(green)
+#endif
+ }
+ }
+#if DEBUG
+ .padding(1)
+ .border(orange)
+#endif
+ .clipped()
+ } // iOS 15
+ }
+ .onChange(of: tappedVal) { newVal in
+ print(">>tapped ", newVal)
+ if newVal > 0 {
+ tappedVal = 0
+ let ms = debugAnimations ? 1250 : 250
+ // next cycle
+ DispatchQueue.main.async {
+ withAnimation(.fly1) {
+ if #available(iOS 16.4, *) {
+ print("\n>>addCash", newVal)
+ cash.addCash(newVal)
+ amountVal += newVal // update directly
+ } else {
+ print("\n>>start flying", newVal, flying)
+ myTappedVal = newVal
+ }
+ }
+
+ print("\n>>reset flying", newVal, flying)
+ flying = 0 // remove immediately after it flew in, but outside of the animation block
+
+
+ if #unavailable(iOS 16.4) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms)) {
+ print("\n>>dissolve", newVal, flying)
+ withAnimation(.basic1) {
+ amountVal += newVal
+ myTappedVal = 0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// MARK: -
+//#Preview {
+// OIMlineView()
+//}
diff --git a/TalerWallet1/Views/OIM/OIMlineViews.swift b/TalerWallet1/Views/OIM/OIMlineViews.swift
@@ -1,199 +0,0 @@
-/*
- * 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 a spread of banknote-stacks in 1 row
-struct OIMnotesView1: View {
- let stack: CallStack
- let spread: OIMdenominations
- let currency: OIMcurrency
- @Binding var amountVal: UInt64
- let tappedVal: UInt64
- @Binding var flying: UInt64
- let shake: Bool
- let canEdit: Bool
-
- var body: some View {
-// let _ = Self._printChanges()
- let nrOfNotes = currency.bankNotes.count - 1
- HStack(alignment: .center, spacing: 10) {
- ForEach(0...nrOfNotes, id: \.self) { index in
- let value = currency.bankNotes[index]
- let shouldFly = tappedVal == value
- let count = spread[index] + (shouldFly ? 1 : 0)
- if count > 0 {
- OIMnoteStackV(stack: stack.push(),
- value: value,
- count: Int(count),
- currency: currency,
- tappedVal: tappedVal,
- flying: $flying,
- shake: shake,
- canEdit: canEdit
- ) {
- withAnimation(.basic1) {
- amountVal -= value // remove on button press
- } } }
- } // ForEach
- } // HStack
- }
-}
-
-// MARK: -
-fileprivate let horzSpacing: CGFloat = 20
-fileprivate let vertSpacing: CGFloat = 10
-
-struct OIMlineViews: View {
- let stack: CallStack
- @Binding var amountVal: UInt64
- @Binding var tappedVal: UInt64
- let canEdit: Bool
-
- @EnvironmentObject private var cash: OIMcash
- @EnvironmentObject private var wrapper: NamespaceWrapper
- @AppStorage("oimTwoRows") var oimTwoRows: Bool = false
-
- @State private var flying: UInt64 = 0
- @State private var myTappedVal: UInt64 = 0
-
- var body: some View {
-// let _ = Self._printChanges()
-#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 currency = cash.currency
- let result = currency.notesCoins(amountVal)
- // Text("notes: \(result.0), coins: \(result.1)")
- let notes = OIMnotesView1(stack: stack.push(),
- spread: result.0,
- currency: currency,
- amountVal: $amountVal,
- tappedVal: myTappedVal,
- flying: $flying,
- shake: (cash.shake != 0),
- canEdit: canEdit
- ).id("notes")
- .matchedGeometryEffect(id: "notes", in: wrapper.namespace)
-#if DEBUG
- .border(blue)
-#endif
- let coins = OIMcoinsView1(stack: stack.push(),
- spread: result.1,
- currency: currency,
- amountVal: $amountVal,
- tappedVal: myTappedVal,
- flying: $flying,
- shake: (cash.shake != 0),
- canEdit: canEdit
- ).id("coins")
- .matchedGeometryEffect(id: "coins", in: wrapper.namespace)
-#if DEBUG
- .border(red)
-#endif
-
- let oneLine = HStack(alignment: .center, 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
- }
- }
-
- Group {
- if #available(iOS 16.4, *) {
- LayoutThatFits([HStackLayout(alignment: .center), VStackLayout()]) {
- notes
-#if DEBUG
- .padding(1)
- .border(green)
-#endif
- coins
- }
-#if DEBUG
- .padding(1)
- .border(orange)
-#endif
- } else {
- scrollView
-#if DEBUG
- .padding(1)
- .border(orange)
-#endif
- .clipped()
- }
- }
- .onChange(of: tappedVal) { newVal in
- print(">>tapped ", newVal)
- if newVal > 0 {
- tappedVal = 0
- let ms = debugAnimations ? 1250 : 250
-
- // next cycle
- DispatchQueue.main.async {
- print("\n>>start flying", newVal, flying)
- let addedValue = (newVal == 200) ? 1000 : newVal
- withAnimation(.shake1) {
- print("addCash", addedValue)
- cash.addCash(addedValue)
- myTappedVal = newVal
- }
- print("\n>>reset flying", newVal, flying)
- flying = 0 // remove immediately after it flew in, but outside of the animation block
-
-
- DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms)) {
- print("\n>>shake start", newVal, flying)
- withAnimation(.shake1) {
- cash.shake = newVal
- }
- DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms)) {
- print("\n>>shake end", newVal, flying)
- withAnimation(.shake1) {
- cash.shake = 0
- }
- DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms)) {
- print("\n>>dissolve", newVal, flying)
- withAnimation(.basic1) {
- amountVal += newVal
- myTappedVal = 0
- }
- }
- }
- }
- }
- }
- }
- }
-}
-
-// MARK: -
-//#Preview {
-// OIMlineView()
-//}