taler-ios

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

commit 7674108ce7ec8a617cfe8b5cb94be73f41e62f69
parent 146b3c2ea63c79a1b99a4dfc01d7557e64e136e3
Author: Marc Stibane <marc@taler.net>
Date:   Thu, 24 Apr 2025 09:30:38 +0200

OIMcurrencyButton

Diffstat:
ATalerWallet1/Views/OIM/OIMcurrencyButton.swift | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MTalerWallet1/Views/OIM/OIMcurrencyScroller.swift | 24++++++++++++------------
DTalerWallet1/Views/OIM/OIMcurrencyViews.swift | 206-------------------------------------------------------------------------------
ATalerWallet1/Views/OIM/OIMcurrencyViews2.swift | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 233 insertions(+), 218 deletions(-)

diff --git a/TalerWallet1/Views/OIM/OIMcurrencyButton.swift b/TalerWallet1/Views/OIM/OIMcurrencyButton.swift @@ -0,0 +1,107 @@ +/* + * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. + * See LICENSE.md + */ +/** + * @author Marc Stibane + */ +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.7 : 1.0) + .scaleEffect(configuration.isPressed ? 0.9 : 1.0) + } +} + +struct OIMcurrencyImage { + let image: Image + let oWidth: CGFloat + let oHeight: CGFloat + + init(_ name: String?) { + if let name, let uiImage = UIImage(named: name) { + self.oWidth = uiImage.size.width + self.oHeight = uiImage.size.height + self.image = Image(uiImage: uiImage) + } else { + self.image = Image(systemName: "exclamationmark.triangle") + self.oWidth = 300 + self.oHeight = 300 + } + } +} + +struct OIMmod: AnimatableModifier { + let value: Int + let name: String? + 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 { + let currencyImage = OIMcurrencyImage(name) + let image = currencyImage.image + .resizable() + .scaledToFit() + Group { + if canEdit && availableVal >= value { + Button(action: action) { + image + } + .buttonStyle(OIMbuttonStyle()) + } else { + image + .opacity(canEdit ? 0.5 : 1.0) + } + } + .frame(width: currencyImage.oWidth / 4, height: currencyImage.oHeight / 4) + .shadow(radius: shadow) + .accessibilityLabel(Text("\(value)", comment: "VoiceOver")) + } + } +} + +/// renders 1 denomination from a currency +struct OIMcurrencyButton: View { + let value: Int + let currency: OIMcurrency + let availableVal: Int + let canEdit: Bool + var pct: CGFloat + let action: () -> Void + + var body: some View { + let name = value > currency.bankCoins[0] ? currency.noteName(value) + : currency.coinName(value) + // Use EmptyView, because the modifier actually ignores + // the value passed to its body() function. + EmptyView().modifier(OIMmod(value: value, + name: name, + availableVal: availableVal, + canEdit: canEdit, + pct: pct, + action: action)) + } +} + +// MARK: - +//#Preview { +// OIMcurrencyView() +//} diff --git a/TalerWallet1/Views/OIM/OIMcurrencyScroller.swift b/TalerWallet1/Views/OIM/OIMcurrencyScroller.swift @@ -53,22 +53,22 @@ struct OIMcurrencyScroller: View { ScrollView(.horizontal) { HStack(alignment: .bottom, spacing: 10) { ForEach(currency.bankNotes, id: \.self) { value in - OIMcurrencyView(value: value, - currency: currency, - availableVal: availableVal, - canEdit: true, - pct: 0.0, - action: { tap(value: value) } + OIMcurrencyButton(value: value, + currency: currency, + availableVal: availableVal, + canEdit: true, + pct: 0.0, + action: { tap(value: value) } ) .matchedGeometryEffect(id: value, in: wrapper.namespace, isSource: true) } ForEach(currency.bankCoins, id: \.self) { value in - OIMcurrencyView(value: value, - currency: currency, - availableVal: availableVal, - canEdit: true, - pct: 0.0, - action: { tap(value: value) } + OIMcurrencyButton(value: value, + currency: currency, + availableVal: availableVal, + canEdit: true, + pct: 0.0, + action: { tap(value: value) } ) .matchedGeometryEffect(id: value, in: wrapper.namespace, isSource: true) } diff --git a/TalerWallet1/Views/OIM/OIMcurrencyViews.swift b/TalerWallet1/Views/OIM/OIMcurrencyViews.swift @@ -1,206 +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 - -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.7 : 1.0) - .scaleEffect(configuration.isPressed ? 0.9 : 1.0) - } -} - -struct OIMcurrencyImage { - let image: Image - let oWidth: CGFloat - let oHeight: CGFloat - - init(_ name: String?) { - if let name, let uiImage = UIImage(named: name) { - self.oWidth = uiImage.size.width - self.oHeight = uiImage.size.height - self.image = Image(uiImage: uiImage) - } else { - self.image = Image(systemName: "exclamationmark.triangle") - self.oWidth = 300 - self.oHeight = 300 - } - } -} - -struct OIMmod: AnimatableModifier { - let value: Int - let name: String? - 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 { - let currencyImage = OIMcurrencyImage(name) - let image = currencyImage.image - .resizable() - .scaledToFit() - Group { - if canEdit && availableVal >= value { - Button(action: action) { - image - } - .buttonStyle(OIMbuttonStyle()) - } else { - image - .opacity(canEdit ? 0.5 : 1.0) - } - } - .frame(width: currencyImage.oWidth / 4, height: currencyImage.oHeight / 4) - .shadow(radius: shadow) - .accessibilityLabel(Text("\(value)", comment: "VoiceOver")) - } - } -} - -/// renders 1 denomination from a currency -struct OIMcurrencyView: View { - let value: Int - let currency: OIMcurrency - let availableVal: Int - let canEdit: Bool - var pct: CGFloat - let action: () -> Void - - var body: some View { - let name = value > currency.bankCoins[0] ? currency.noteName(value) - : currency.coinName(value) - // Use EmptyView, because the modifier actually ignores - // the value passed to its body() function. - EmptyView().modifier(OIMmod(value: value, - name: name, - availableVal: availableVal, - canEdit: canEdit, - pct: pct, - action: action)) - } -} - -// MARK: - -// renders a stack of (identical) banknotes with offset 10,20 -struct OIMnoteStackV: View { - let value: Int - let count: Int - let currency: OIMcurrency - let tappedVal: Int - @Binding var flying: Int - 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 id = match && !isFlying ? value : -value - 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)") - OIMcurrencyView(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: 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)) - } -} - -// renders a stack of (identical) coins with offset size/16 -struct OIMcoinStackV: View { - let value: Int - let count: Int - let currency: OIMcurrency - let tappedVal: Int - @Binding var flying: Int - 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 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 - OIMcurrencyView(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: 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: - -//#Preview { -// OIMstackV() -//} diff --git a/TalerWallet1/Views/OIM/OIMcurrencyViews2.swift b/TalerWallet1/Views/OIM/OIMcurrencyViews2.swift @@ -0,0 +1,114 @@ +/* + * 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 value: Int + let count: Int + let currency: OIMcurrency + let tappedVal: Int + @Binding var flying: Int + 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 id = match && !isFlying ? value : -value + 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(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: 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)) + } +} + +// renders a stack of (identical) coins with offset size/16 +struct OIMcoinStackV: View { + let value: Int + let count: Int + let currency: OIMcurrency + let tappedVal: Int + @Binding var flying: Int + 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 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 + OIMcurrencyButton(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: 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: - +//#Preview { +// OIMstackV() +//}