commit e204f1afe4459fcb1e6b87d2dc6e714b3b81b130 parent 25995f0a018167478ee325e2df9a07782cbd1665 Author: Marc Stibane <marc@taler.net> Date: Sun, 16 Aug 2026 19:02:55 +0200 move ViewModifier to TalerCommon Diffstat:
16 files changed, 161 insertions(+), 166 deletions(-)
diff --git a/TalerCommon/ViewModifier/Color+hex.swift b/TalerCommon/ViewModifier/Color+hex.swift @@ -0,0 +1,17 @@ +/* + * This file is part of GNU Taler, ©2022-26 Taler Systems S.A. + * See LICENSE.md + */ +/** + * @author Marc Stibane + */ +import SwiftUI + +extension Color { + init(hex: Int, opacity: Double = 1.0) { + let red = Double((hex >> 16) & 0xff) / 255.0 + let green = Double((hex >> 8) & 0xff) / 255.0 + let blue = Double( hex & 0xff) / 255.0 + self.init(.sRGB, red: red, green: green, blue: blue, opacity: opacity) + } +} diff --git a/TalerWallet1/Views/ViewModifier/Environment+EdgeInsets.swift b/TalerCommon/ViewModifier/Environment+EdgeInsets.swift diff --git a/TalerWallet1/Views/ViewModifier/Shape+style.swift b/TalerCommon/ViewModifier/Shape+style.swift diff --git a/TalerWallet1/Views/ViewModifier/View+Condition.swift b/TalerCommon/ViewModifier/View+Condition.swift diff --git a/TalerWallet1/Views/ViewModifier/View+DeviceRotation.swift b/TalerCommon/ViewModifier/View+DeviceRotation.swift diff --git a/TalerWallet1/Views/ViewModifier/View+Keyboard.swift b/TalerCommon/ViewModifier/View+Keyboard.swift diff --git a/TalerWallet1/Views/ViewModifier/View+Notification.swift b/TalerCommon/ViewModifier/View+Notification.swift diff --git a/TalerWallet1/Views/ViewModifier/View+dismissTop.swift b/TalerCommon/ViewModifier/View+dismissTop.swift diff --git a/TalerCommon/ViewModifier/View+fitsSideBySide.swift b/TalerCommon/ViewModifier/View+fitsSideBySide.swift @@ -0,0 +1,81 @@ +/* + * This file is part of GNU Taler, ©2022-26 Taler Systems S.A. + * See LICENSE.md + */ +/** + * @author Marc Stibane + */ +import SwiftUI +import UIKit + +extension View { + @MainActor + public func announce(_ this: String) { + if UIAccessibility.isVoiceOverRunning { + UIAccessibility.post(notification: .announcement, argument: this) + } + } + @MainActor + public func setVoice(to this: (any View)?) { + if UIAccessibility.isVoiceOverRunning { + UIAccessibility.post(notification: .layoutChanged, argument: this) + } + } +} + +extension View { + /// if sameSize then this searches for the longest title + /// returns true if any of the strings in 'titles' wouldn't fit in a view 1/'numViews' of the size of 'width', with 'spacing' + /// if !sameSize then all titles are added with spacing + static func fitsSideBySide(_ titles: [(String, UIFont)], + availableWidth: CGFloat, // total width available + sizeCategory: ContentSizeCategory, + spacing: CGFloat = HSPACING, // between titles + padding: CGFloat = 20, + sameSize: Bool = true, + numViews: Int = 2) + -> Bool { +// let padding = 20.0 // TODO: depend on myListStyle + let totalSpacing = spacing * CGFloat(numViews - 1) + var totalWidth = padding + totalSpacing + + var maxTitleWidth = 0.0 + var minTitleWidth = 1000.0 + for (title, uiFont) in titles { + let titleWidth = title.widthOfString(usingUIFont: uiFont, sizeCategory) + if titleWidth > maxTitleWidth { + maxTitleWidth = titleWidth + } + if titleWidth < minTitleWidth { + minTitleWidth = titleWidth + } +// if availableWidth > 20 { +// let widthStr = String(format: "%.2f + %.2f = %.2f", totalWidth, titleWidth, totalWidth + titleWidth) +// print("❗️ \(title) \(widthStr)") +// } + totalWidth += titleWidth + } + + if sameSize { + let nettoWidth = availableWidth - totalSpacing + let singleWidth = nettoWidth / CGFloat(numViews) + let neededWidth = padding + maxTitleWidth +// if availableWidth > 20 { +// let width1Str = String(format: "%.2f -spacing %.2f = %.2f / %d = %.2f", availableWidth, totalSpacing, +// nettoWidth, numViews, singleWidth) +// let width2Str = String(format: "%.2f +padding %.2f = %.2f", maxTitleWidth, padding, maxTitleWidth + padding) +// print("❗️ available: \(width1Str) needed: \(width2Str)") +// } + return neededWidth < singleWidth + } else { +// if availableWidth > 20 { +// let totalStr = String(format: "%.2f -spacing %.2f = %.2f -padding %.2f = %.2f", totalWidth, totalSpacing, +// totalWidth - totalSpacing, padding, totalWidth - totalSpacing - padding) +// print("❗️ view width: \(availableWidth) total: \(totalStr)") +// let (amount, font) = titles[1] +// print("❗️ view width: \(width) total: \(totalStr) min: \(minTitleWidth) max: \(maxTitleWidth) \(amount)") +// } + return totalWidth < availableWidth + } + } +} diff --git a/TalerCommon/ViewModifier/View+flippedDirection.swift b/TalerCommon/ViewModifier/View+flippedDirection.swift @@ -0,0 +1,63 @@ +/* + * This file is part of GNU Taler, ©2022-26 Taler Systems S.A. + * See LICENSE.md + */ +/** + * @author Marc Stibane + */ +import SwiftUI + +struct FlippedDirection: ViewModifier { + @Environment(\.layoutDirection) var layoutDirection + + func body(content: Content) -> some View { + let isLeft = layoutDirection == .leftToRight + content + .environment(\.layoutDirection, isLeft ? .rightToLeft : .leftToRight) + } +} + +extension View { + func flippedDirection() -> some View { + self.modifier(FlippedDirection()) + } +} +// MARK: - +struct FlippedLeftRight: ViewModifier { + func body(content: Content) -> some View { + content + .rotationEffect(.radians(Double.pi)) + .scaleEffect(x: 1, y: -1, anchor: .center) + } +} + +extension View { + func flippedLeftRight() -> some View { + modifier(FlippedLeftRight()) + } +} +// MARK: - +struct FlippedUpsideDown: ViewModifier { + func body(content: Content) -> some View { + content + .rotationEffect(.radians(Double.pi)) + .scaleEffect(x: -1, y: 1, anchor: .center) + } +} + +extension View { + func flippedUpsideDown() -> some View { + modifier(FlippedUpsideDown()) + } +} + + +// Used to reverse the direction of a ScrollView +// need to flip both the ScrollView and each list item +// inspired by https://stackoverflow.com/questions/61520970/swiftui-reverse-scrollview-from-bottom-to-top +// +//Apple has an API when using iOS 17+ +// ScrollView() { +// // content +// } +// .defaultScrollAnchor(.bottom) diff --git a/TalerWallet1/Views/ViewModifier/View+innerSize.swift b/TalerCommon/ViewModifier/View+innerSize.swift diff --git a/TalerWallet1/Views/ViewModifier/View+viewExtractor.swift b/TalerCommon/ViewModifier/View+viewExtractor.swift diff --git a/TalerWallet1/Views/ViewModifier/Color+hex.swift b/TalerWallet1/Views/ViewModifier/Color+hex.swift @@ -1,17 +0,0 @@ -/* - * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. - * See LICENSE.md - */ -/** - * @author Marc Stibane - */ -import SwiftUI - -extension Color { - init(hex: Int, opacity: Double = 1.0) { - let red = Double((hex >> 16) & 0xff) / 255.0 - let green = Double((hex >> 8) & 0xff) / 255.0 - let blue = Double( hex & 0xff) / 255.0 - self.init(.sRGB, red: red, green: green, blue: blue, opacity: opacity) - } -} diff --git a/TalerWallet1/Views/ViewModifier/View+fitsSideBySide.swift b/TalerWallet1/Views/ViewModifier/View+fitsSideBySide.swift @@ -1,81 +0,0 @@ -/* - * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. - * See LICENSE.md - */ -/** - * @author Marc Stibane - */ -import SwiftUI -import UIKit - -extension View { - @MainActor - public func announce(_ this: String) { - if UIAccessibility.isVoiceOverRunning { - UIAccessibility.post(notification: .announcement, argument: this) - } - } - @MainActor - public func setVoice(to this: (any View)?) { - if UIAccessibility.isVoiceOverRunning { - UIAccessibility.post(notification: .layoutChanged, argument: this) - } - } -} - -extension View { - /// if sameSize then this searches for the longest title - /// returns true if any of the strings in 'titles' wouldn't fit in a view 1/'numViews' of the size of 'width', with 'spacing' - /// if !sameSize then all titles are added with spacing - static func fitsSideBySide(_ titles: [(String, UIFont)], - availableWidth: CGFloat, // total width available - sizeCategory: ContentSizeCategory, - spacing: CGFloat = HSPACING, // between titles - padding: CGFloat = 20, - sameSize: Bool = true, - numViews: Int = 2) - -> Bool { -// let padding = 20.0 // TODO: depend on myListStyle - let totalSpacing = spacing * CGFloat(numViews - 1) - var totalWidth = padding + totalSpacing - - var maxTitleWidth = 0.0 - var minTitleWidth = 1000.0 - for (title, uiFont) in titles { - let titleWidth = title.widthOfString(usingUIFont: uiFont, sizeCategory) - if titleWidth > maxTitleWidth { - maxTitleWidth = titleWidth - } - if titleWidth < minTitleWidth { - minTitleWidth = titleWidth - } -// if availableWidth > 20 { -// let widthStr = String(format: "%.2f + %.2f = %.2f", totalWidth, titleWidth, totalWidth + titleWidth) -// print("❗️ \(title) \(widthStr)") -// } - totalWidth += titleWidth - } - - if sameSize { - let nettoWidth = availableWidth - totalSpacing - let singleWidth = nettoWidth / CGFloat(numViews) - let neededWidth = padding + maxTitleWidth -// if availableWidth > 20 { -// let width1Str = String(format: "%.2f -spacing %.2f = %.2f / %d = %.2f", availableWidth, totalSpacing, -// nettoWidth, numViews, singleWidth) -// let width2Str = String(format: "%.2f +padding %.2f = %.2f", maxTitleWidth, padding, maxTitleWidth + padding) -// print("❗️ available: \(width1Str) needed: \(width2Str)") -// } - return neededWidth < singleWidth - } else { -// if availableWidth > 20 { -// let totalStr = String(format: "%.2f -spacing %.2f = %.2f -padding %.2f = %.2f", totalWidth, totalSpacing, -// totalWidth - totalSpacing, padding, totalWidth - totalSpacing - padding) -// print("❗️ view width: \(availableWidth) total: \(totalStr)") -// let (amount, font) = titles[1] -// print("❗️ view width: \(width) total: \(totalStr) min: \(minTitleWidth) max: \(maxTitleWidth) \(amount)") -// } - return totalWidth < availableWidth - } - } -} diff --git a/TalerWallet1/Views/ViewModifier/View+flippedDirection.swift b/TalerWallet1/Views/ViewModifier/View+flippedDirection.swift @@ -1,63 +0,0 @@ -/* - * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. - * See LICENSE.md - */ -/** - * @author Marc Stibane - */ -import SwiftUI - -struct FlippedDirection: ViewModifier { - @Environment(\.layoutDirection) var layoutDirection - - func body(content: Content) -> some View { - let isLeft = layoutDirection == .leftToRight - content - .environment(\.layoutDirection, isLeft ? .rightToLeft : .leftToRight) - } -} - -extension View { - func flippedDirection() -> some View { - self.modifier(FlippedDirection()) - } -} -// MARK: - -struct FlippedLeftRight: ViewModifier { - func body(content: Content) -> some View { - content - .rotationEffect(.radians(Double.pi)) - .scaleEffect(x: 1, y: -1, anchor: .center) - } -} - -extension View { - func flippedLeftRight() -> some View { - modifier(FlippedLeftRight()) - } -} -// MARK: - -struct FlippedUpsideDown: ViewModifier { - func body(content: Content) -> some View { - content - .rotationEffect(.radians(Double.pi)) - .scaleEffect(x: -1, y: 1, anchor: .center) - } -} - -extension View { - func flippedUpsideDown() -> some View { - modifier(FlippedUpsideDown()) - } -} - - -// Used to reverse the direction of a ScrollView -// need to flip both the ScrollView and each list item -// inspired by https://stackoverflow.com/questions/61520970/swiftui-reverse-scrollview-from-bottom-to-top -// -//Apple has an API when using iOS 17+ -// ScrollView() { -// // content -// } -// .defaultScrollAnchor(.bottom) diff --git a/TalerWalletXC.xcodeproj/project.pbxproj b/TalerWalletXC.xcodeproj/project.pbxproj @@ -199,7 +199,6 @@ 4E4A40DD2CD5737100CA6A90 /* Quickjs */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Quickjs; sourceTree = "<group>"; }; 4E4A40E42CD573D500CA6A90 /* Sounds */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Sounds; sourceTree = "<group>"; }; 4E4A41252CD5769900CA6A90 /* Fonts */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Fonts; sourceTree = "<group>"; }; - 4E77D0ED2D7C3A12009756A5 /* ViewModifier */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ViewModifier; sourceTree = "<group>"; }; 4E9C5A922E58ADB3002E2709 /* Frameworks */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (4E5560C42FEA452200412771 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 4E5560C32FEA44FC00412771 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 4E5560C52FEA452D00412771 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Frameworks; sourceTree = "<group>"; }; 4EB66D772E18238000BE0651 /* Resources */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (4EB66D782E18238B00BE0651 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 4EB66D792E18239300BE0651 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 4EB66D7A2E18239300BE0651 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Resources; sourceTree = "<group>"; }; 4ED789A23031BCC9009D7F39 /* TalerCommon */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = TalerCommon; sourceTree = "<group>"; }; @@ -312,7 +311,6 @@ 4E4A3FA12CD5717F00CA6A90 /* Settings */, 4E4A3FE82CD5720900CA6A90 /* Sheets */, 4E4A40262CD5725A00CA6A90 /* HelperViews */, - 4E77D0ED2D7C3A12009756A5 /* ViewModifier */, ); path = Views; sourceTree = "<group>"; @@ -392,7 +390,6 @@ 4E4A40DD2CD5737100CA6A90 /* Quickjs */, 4E4A40E42CD573D500CA6A90 /* Sounds */, 4E4A41252CD5769900CA6A90 /* Fonts */, - 4E77D0ED2D7C3A12009756A5 /* ViewModifier */, 4E9C5A922E58ADB3002E2709 /* Frameworks */, 4EB66D772E18238000BE0651 /* Resources */, 4ED789A23031BCC9009D7F39 /* TalerCommon */, @@ -440,7 +437,6 @@ 4E4A40DD2CD5737100CA6A90 /* Quickjs */, 4E4A40E42CD573D500CA6A90 /* Sounds */, 4E4A41252CD5769900CA6A90 /* Fonts */, - 4E77D0ED2D7C3A12009756A5 /* ViewModifier */, 4E9C5A922E58ADB3002E2709 /* Frameworks */, 4EB66D772E18238000BE0651 /* Resources */, 4ED789A23031BCC9009D7F39 /* TalerCommon */, @@ -488,7 +484,6 @@ 4E4A40DD2CD5737100CA6A90 /* Quickjs */, 4E4A40E42CD573D500CA6A90 /* Sounds */, 4E4A41252CD5769900CA6A90 /* Fonts */, - 4E77D0ED2D7C3A12009756A5 /* ViewModifier */, 4E9C5A922E58ADB3002E2709 /* Frameworks */, 4EB66D772E18238000BE0651 /* Resources */, 4ED789A23031BCC9009D7F39 /* TalerCommon */,