taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

commit c0e8da621faea384b45f194bf151b0b289ee9a5f
parent 06639e32ddaf19ba1c735ac9279c2045051ff1d4
Author: Marc Stibane <marc@taler.net>
Date:   Mon, 31 Mar 2025 21:05:59 +0200

flying

Diffstat:
MTalerWallet1/Views/OIM/OIMView.swift | 51+++++++++++++++++++++++++--------------------------
MTalerWallet1/Views/OIM/OIMcurrencyScroller.swift | 35+++++++++++++++++++++--------------
MTalerWallet1/Views/OIM/OIMcurrencyViews.swift | 71+++++++++++++++++++++++++++++++++++++++++++++++++----------------------
MTalerWallet1/Views/OIM/OIMlineViews.swift | 61++++++++++++++++++++++++++++++++-----------------------------
4 files changed, 127 insertions(+), 91 deletions(-)

diff --git a/TalerWallet1/Views/OIM/OIMView.swift b/TalerWallet1/Views/OIM/OIMView.swift @@ -92,11 +92,15 @@ struct OIMnavBack<Content: View>: View { content() VStack { HStack { - BackButton() { dismiss() } + BackButton() { + print("dismiss") + dismiss() + }.padding(.leading, 4) Spacer() + let inset = UIScreen.horzInsets ForwardButton(enabled: !amount.isZero) { buttonSelected = true - }.padding(.trailing, UIScreen.horzInsets) + }.padding(.trailing, inset > 0 ? inset - 12 : 0) } } .ignoresSafeArea(edges: .horizontal) @@ -117,14 +121,12 @@ struct OIMView: View { @State private var amountVal: Int = 0 @Namespace var namespace - @State private var tappedVal: Int = 0 - @State private var isFlyingToDeck = false + @State private var tappedVal = 0 + @State private var flying = 0 @State private var shake = false var body: some View { -#if PRINT_CHANGES || true - let _ = Self._printChanges() -#endif +// let _ = Self._printChanges() let currency = sierraLeone ? OIMleones : OIMeuros let actions = HStack(spacing: 30) { @@ -146,7 +148,7 @@ struct OIMView: View { amountVal: $amountVal, namespace: namespace, tappedVal: tappedVal, - isFlyingToDeck: $isFlyingToDeck, + flying: $flying, shake: shake, canEdit: false) Spacer() @@ -167,14 +169,12 @@ struct OIMPayView: View { @State private var amountVal: Int = 0 @Namespace var namespace - @State private var tappedVal: Int = 0 - @State private var isFlyingToDeck = false + @State private var tappedVal = 0 + @State private var flying = 0 @State private var shake = false var body: some View { -#if PRINT_CHANGES || true - let _ = Self._printChanges() -#endif +// let _ = Self._printChanges() let currency = sierraLeone ? OIMleones : OIMeuros OIMbackground(amount: amount, currStr: currency.noteBase) { @@ -184,7 +184,7 @@ struct OIMPayView: View { amountVal: $amountVal, namespace: namespace, tappedVal: tappedVal, - isFlyingToDeck: $isFlyingToDeck, + flying: $flying, shake: shake, canEdit: true) Spacer() @@ -210,14 +210,12 @@ struct OIMEditView: View { @State private var availableVal: Int = 0 @Namespace var namespace - @State private var tappedVal: Int = 0 - @State private var isFlyingToDeck = false + @State private var tappedVal = 0 + @State private var flying = 0 @State private var shake = false var body: some View { -#if PRINT_CHANGES || true - let _ = Self._printChanges() -#endif +// let _ = Self._printChanges() let currency = sierraLeone ? OIMleones : OIMeuros OIMnavBack(stack: stack.push(), @@ -226,15 +224,17 @@ struct OIMEditView: View { amount: $amount, buttonSelected: $buttonSelected ) { - VStack { + ZStack { // without this, money would fly below the scroller + VStack { // even though this is the only item in the ZStack Spacer() OIMlineView(currency: currency, amountVal: $amountVal, namespace: namespace, tappedVal: tappedVal, - isFlyingToDeck: $isFlyingToDeck, + flying: $flying, shake: shake, canEdit: true) + .zIndex(1) // make notes fly from topZ .onChange(of: amountVal) { newVal in let currencyStr = amount.currencyStr amount = Amount(currency: currencyStr, cent: UInt64(newVal)) @@ -246,13 +246,14 @@ struct OIMEditView: View { amountVal: $amountVal, namespace: namespace, tappedVal: $tappedVal, - isFlyingToDeck: $isFlyingToDeck, + flying: $flying, shake: $shake) .onChange(of: available) { newVal in availableVal = intValue(newVal) - intValue(amount) } + } + .border(.red) } - .border(.red) }.task { amountVal = intValue(amount) availableVal = intValue(available) - intValue(amount) @@ -270,9 +271,7 @@ struct OIMSubjectView: View { @AppStorage("sierraLeone") var sierraLeone: Bool = false var body: some View { -#if PRINT_CHANGES || true - let _ = Self._printChanges() -#endif +// let _ = Self._printChanges() let currency = sierraLeone ? OIMleones : OIMeuros OIMnavBack(stack: stack.push(), diff --git a/TalerWallet1/Views/OIM/OIMcurrencyScroller.swift b/TalerWallet1/Views/OIM/OIMcurrencyScroller.swift @@ -15,28 +15,34 @@ struct OIMcurrencyScroller: View { @Binding var amountVal: Int let namespace: Namespace.ID @Binding var tappedVal: Int - @Binding var isFlyingToDeck: Bool + @Binding var flying: Int @Binding var shake: Bool - func tap(value: Int, _ delay: Int = 300) { - isFlyingToDeck = false - tappedVal = value + func tap(value: Int, _ delay: Int = 250) { + withAnimation(.shake1) { + flying = 0 + tappedVal = value + } let ms = debugAnimations ? 1500 : delay - print("tapped \(value)") -// DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms)) { +// print("tapped \(value)") + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms)) { // print("shake start") -// withAnimation(.shake) { -// shake = true -// } + withAnimation(.shake1) { + shake = true + } DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms)) { - print("shake end") - withAnimation(. shake1) { +// print("shake end") + withAnimation(.shake1) { shake = false - amountVal += value - tappedVal = 0 + } + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms)) { + withAnimation(.basic1) { + amountVal += value + tappedVal = 0 + } } } -// } + } } var body: some View { ScrollView(.horizontal) { @@ -56,6 +62,7 @@ struct OIMcurrencyScroller: View { currency: currency, availableVal: availableVal, canEdit: true, + pct: 0.0, action: { tap(value: value) } ) .matchedGeometryEffect(id: value, in: namespace, isSource: true) diff --git a/TalerWallet1/Views/OIM/OIMcurrencyViews.swift b/TalerWallet1/Views/OIM/OIMcurrencyViews.swift @@ -32,9 +32,7 @@ struct OIMnoteV: View { let action: () -> Void var body: some View { -#if PRINT_CHANGES || true - let _ = Self._printChanges() -#endif +// let _ = Self._printChanges() // Use EmptyView, because the modifier actually ignores // the value passed to its body() function. EmptyView().modifier(OIMmod(value: value, @@ -93,9 +91,11 @@ struct OIMcoinV: View { let currency: OIMcurrency let availableVal: Int let canEdit: Bool + var pct: CGFloat let action: () -> Void var body: some View { + let shadow = (3 - 8) * pct + 8 if let name = currency.coinName(value), let size = currency.coinSize(value) { let image = Image(name) .resizable() @@ -112,6 +112,7 @@ struct OIMcoinV: View { } } .frame(width: size / 4, height: size / 4) + .shadow(radius: shadow) .accessibilityLabel(Text("\(value)", comment: "VoiceOver")) } else { EmptyView() @@ -132,50 +133,51 @@ struct OIMsingleV: View { if value > currency.bankCoins[0] { OIMnoteV(value: value, currency: currency, availableVal: availableVal, canEdit: canEdit, pct: pct, action: action) } else { - OIMcoinV(value: value, currency: currency, availableVal: availableVal, canEdit: canEdit, action: action) + OIMcoinV(value: value, currency: currency, availableVal: availableVal, canEdit: canEdit, pct: pct, action: action) } } } // MARK: - -// renders a stack of (identical) banknotes with offset 20 +// renders a stack of (identical) banknotes with offset 10,20 struct OIMnoteStackV: View { let value: Int let count: Int let currency: OIMcurrency let namespace: Namespace.ID let tappedVal: Int - @Binding var isFlyingToDeck: Bool + @Binding var flying: Int let shake: Bool let canEdit: Bool let action: () -> Void var body: some View { -#if PRINT_CHANGES || true - let _ = Self._printChanges() -#endif +// let _ = Self._printChanges() let maxIndex = count - 1 ZStack { ForEach(0...maxIndex, id: \.self) { index in let match = tappedVal == value && index == maxIndex - let id = match && !isFlyingToDeck ? value : -value + let isFlying = flying > 0 + let id = match && !isFlying ? 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)") + let shakeOffset: CGFloat = shake && !match ? .random(in: 10...25) + : .zero + let direction = value < tappedVal +// let _ = print("id \(id), flying \(flying), 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) + .offset(x: xOffset + (direction ? shakeOffset : -shakeOffset), + y: yOffset) .matchedGeometryEffect(id: id, in: namespace, isSource: false) .onAppear { - print("start flying \(id), shaking \(shakeOffset)") +// print("start flying \(id), shaking \(shakeOffset)") withAnimation(.fly1) { - isFlyingToDeck = true + flying = value // start flying } } } @@ -190,21 +192,46 @@ struct OIMcoinStackV: View { let value: Int let count: Int let currency: OIMcurrency + let namespace: Namespace.ID + let tappedVal: Int + @Binding var flying: Int + let shake: Bool let canEdit: Bool let action: () -> Void var body: some View { - let number = count - 1 + let maxIndex = count - 1 if let size = currency.coinSize(value) { let offset = size / 16 ZStack { - ForEach(0...number, id: \.self) { index in - OIMcoinV(value: value, currency: currency, availableVal: value, canEdit: canEdit, action: action) - .offset(x: offset * CGFloat(index), y: offset * CGFloat(index)) + ForEach(0...maxIndex, id: \.self) { index in + let match = tappedVal == value && index == maxIndex + let isFlying = flying > 0 + let id = match && !isFlying ? value : -value + let yOffset = offset * CGFloat(index) + let xOffset = yOffset / 2 + let shakeOffset: CGFloat = shake && !match ? .random(in: 5...10) + : .zero + let direction = value < tappedVal + OIMcoinV(value: value, + currency: currency, + availableVal: value, + canEdit: canEdit, + pct: match ? 0.0 : 1.0, + action: action) + .offset(x: xOffset + (direction ? shakeOffset : -shakeOffset), + y: yOffset) + .matchedGeometryEffect(id: id, in: namespace, isSource: false) + .onAppear { +// print("start flying \(id), shaking \(shakeOffset)") + withAnimation(.fly1) { + flying = value // start flying + } + } } } - .padding(.trailing, offset * CGFloat(number)) - .padding(.bottom, offset * CGFloat(number)) + .padding(.trailing, offset * CGFloat(maxIndex)) + .padding(.bottom, offset * CGFloat(maxIndex)) } } } diff --git a/TalerWallet1/Views/OIM/OIMlineViews.swift b/TalerWallet1/Views/OIM/OIMlineViews.swift @@ -15,38 +15,33 @@ struct OIMnotesView1: View { @Binding var amountVal: Int let namespace: Namespace.ID let tappedVal: Int - @Binding var isFlyingToDeck: Bool + @Binding var flying: Int let shake: Bool let canEdit: Bool var body: some View { -#if PRINT_CHANGES || true - let _ = Self._printChanges() -#endif +// let _ = Self._printChanges() let nrOfNotes = currency.bankNotes.count - 1 HStack(alignment: .top, spacing: 10) { ForEach(0...nrOfNotes, id: \.self) { index in - let count = spread[index] let value = currency.bankNotes[index] - let flyingVal = tappedVal == value - if count > 0 || flyingVal { + let shouldFly = tappedVal == value + let count = spread[index] + (shouldFly ? 1 : 0) + if count > 0 { OIMnoteStackV(value: value, - count: count + (flyingVal ? 1 : 0), + count: count, currency: currency, namespace: namespace, tappedVal: tappedVal, - isFlyingToDeck: $isFlyingToDeck, + flying: $flying, shake: shake, canEdit: canEdit ) { withAnimation(.basic1) { amountVal -= value // remove on button press - } - } - .matchedGeometryEffect(id: tappedVal, in: namespace, isSource: false) - } - } - } + } } } + } // ForEach + } // HStack } } @@ -57,26 +52,33 @@ struct OIMcoinsView1: View { let currency: OIMcurrency @Binding var amountVal: Int let namespace: Namespace.ID + let tappedVal: Int + @Binding var flying: Int + 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 count = spread[index] + let value = currency.bankCoins[index] + let shouldFly = tappedVal == value + let count = spread[index] + (shouldFly ? 1 : 0) if count > 0 { - let value = currency.bankCoins[index] OIMcoinStackV(value: value, count: count, currency: currency, - canEdit: canEdit) { + namespace: namespace, + tappedVal: tappedVal, + flying: $flying, + shake: shake, + canEdit: canEdit + ) { withAnimation(Animation.easeIn1) { amountVal -= value - } - } - } - } - } + } } } + } // ForEach + } // HStack } } @@ -89,16 +91,14 @@ struct OIMlineView: View { @Binding var amountVal: Int let namespace: Namespace.ID let tappedVal: Int - @Binding var isFlyingToDeck: Bool + @Binding var flying: Int let shake: Bool let canEdit: Bool @AppStorage("oimTwoRows") var oimTwoRows: Bool = false var body: some View { -#if PRINT_CHANGES || true - let _ = Self._printChanges() -#endif +// let _ = Self._printChanges() #if DEBUG let debug = 1==0 let red = debug ? Color.red : Color.clear @@ -113,7 +113,7 @@ struct OIMlineView: View { amountVal: $amountVal, namespace: namespace, tappedVal: tappedVal, - isFlyingToDeck: $isFlyingToDeck, + flying: $flying, shake: shake, canEdit: canEdit ).id("notes") @@ -125,6 +125,9 @@ struct OIMlineView: View { currency: currency, amountVal: $amountVal, namespace: namespace, + tappedVal: tappedVal, + flying: $flying, + shake: shake, canEdit: canEdit ).id("coins") .matchedGeometryEffect(id: "coins", in: namespace) @@ -176,7 +179,7 @@ struct OIMlineView: View { } } } else { - LayoutThatFits([HStackLayout(), VStackLayout()]) { + LayoutThatFits([HStackLayout(alignment: .top), VStackLayout()]) { notes #if DEBUG .padding(1)