commit aaf5850aece1529a12d355b8876cd842f69ce3f1
parent 3ca95c182906b79a354d01bfa9dcc330e26e2889
Author: Marc Stibane <marc@taler.net>
Date: Wed, 24 Jun 2026 08:21:44 +0200
fix a11y for SettingsButton
Diffstat:
4 files changed, 69 insertions(+), 45 deletions(-)
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -20,6 +20,7 @@ struct BalancesListView: View {
@EnvironmentObject private var model: WalletModel
@EnvironmentObject private var controller: Controller
+ @Environment(\.accessibilityVoiceOverEnabled) private var voiceOverEnabled
@AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
@AppStorage("oimEuro") var oimEuro: Bool = false
@@ -79,20 +80,12 @@ struct BalancesListView: View {
if #available(iOS 26.0, *) {
let list26 = list
.toolbar {
- ToolbarItem(placement: .primaryAction) {
- Button {
- NotificationCenter.default.post(name: .SettingsAction, object: nil) // will trigger NavigationLink
- } label: {
- Label(TalerTab.settings.title, systemImage: TalerTab.settings.sysImg)
- .labelStyle(.iconOnly)
- }
- // .padding()
- .buttonStyle(.glass)
-// .accessibilitySortPriority(0)
+ ToolbarItem(placement: .topBarTrailing) { //secondaryAction
+ SettingsButton26(sortPriority: 0)
}
}
#if OIM
- if controller.oimModeActive {
+ if controller.oimModeActive || voiceOverEnabled {
list // hide SettingsButton (in Toolbar)
} else {
list26
diff --git a/TalerWallet1/Views/HelperViews/Buttons.swift b/TalerWallet1/Views/HelperViews/Buttons.swift
@@ -173,6 +173,24 @@ struct PlusButton : View {
}
}
+@available(iOS 26.0, *)
+struct SettingsButton26 : View {
+ let sortPriority: Double
+
+ var body: some View {
+ Button {
+ // will trigger NavigationLink
+ NotificationCenter.default.post(name: .SettingsAction, object: nil)
+ } label: {
+ Label(TalerTab.settings.title, systemImage: TalerTab.settings.sysImg)
+ .labelStyle(.iconOnly)
+ }
+// .padding()
+ .buttonStyle(.glass)
+ .accessibilitySortPriority(sortPriority)
+ }
+}
+
struct SettingsButton : View {
let accessibilityLabelStr: String
let action: () -> Void
diff --git a/TalerWallet1/Views/Main/WalletEmptyView.swift b/TalerWallet1/Views/Main/WalletEmptyView.swift
@@ -119,6 +119,7 @@ struct WalletEmptyHeader: View {
@EnvironmentObject private var model: WalletModel
@EnvironmentObject private var controller: Controller
+ @Environment(\.accessibilityVoiceOverEnabled) private var voiceOverEnabled
func refresh() async {
controller.hapticNotification(.success)
@@ -129,12 +130,13 @@ struct WalletEmptyHeader: View {
var body: some View {
let talerLogo = HStack(spacing: 2) {
Image(TALER_LOGO_FULL)
- // .border(Color.gray, width: 1)
- // Text("TALER Wallet")
+// .border(Color.gray, width: 1)
+// Text("TALER Wallet")
Text("Wallet")
- // .font(.logo(27, weight: .medium))
+// .font(.logo(27, weight: .medium))
.font(.logo("Atkinson Hyperlegible Next", size: 27, weight: .medium))
.kerning(1.0)
+ .fixedSize(horizontal: true, vertical: true)
}
.accessibilityElement(children: .combine)
.accessibilityAddTraits(.isHeader)
@@ -151,22 +153,26 @@ struct WalletEmptyHeader: View {
.padding(.top, 10)
}
if #available(iOS 26.0, *) {
- emptyView
- .toolbar {
- logoItem
- ToolbarItem(placement: .primaryAction) { // Settings button
- Button {
- NotificationCenter.default.post(name: .SettingsAction, object: nil) // will trigger NavigationLink
- } label: {
- Label(TalerTab.settings.title, systemImage: TalerTab.settings.sysImg)
- .labelStyle(.iconOnly)
+ if voiceOverEnabled {
+ emptyView
+ .toolbar { logoItem }
+ } else {
+ emptyView
+ .toolbar {
+ ToolbarItem(placement: .principal) {
+ HStack {
+ SettingsButton26(sortPriority: 0)
+ .hidden()
+ Spacer()
+ talerLogo
+ .padding(.top, 10)
+ Spacer()
+ SettingsButton26(sortPriority: 0)
+ .offset(x: 16, y: 0)
+ }
}
- // .padding()
- .buttonStyle(.glass)
- .accessibilitySortPriority(0)
-
}
- }
+ }
} else { // iOS 15..18 Settings is the 3rd tab
emptyView
.toolbar { logoItem }
diff --git a/TalerWallet1/Views/Main/WalletMain.swift b/TalerWallet1/Views/Main/WalletMain.swift
@@ -32,6 +32,7 @@ struct WalletMain: View {
@EnvironmentObject private var viewState: ViewState // popToRootView()
@EnvironmentObject private var viewState2: ViewState2 // popToRootView()
@EnvironmentObject private var wrapper: NamespaceWrapper
+ @Environment(\.accessibilityVoiceOverEnabled) private var voiceOverEnabled
#if DEBUG
@AppStorage("developerMode") var developerMode: Bool = true
#else
@@ -170,7 +171,7 @@ struct WalletMain: View {
}
/// NavigationViews for Balances & Settings
let balancesStack = NavigationView {
- ZStack(alignment: .bottom) {
+ ZStack(alignment: .trailing) {
if controller.balances.isEmpty {
WalletEmptyHeader(stack: stack.push())
} else {
@@ -183,38 +184,43 @@ struct WalletMain: View {
title: balancesTitle,
selectedBalance: $selectedBalance, // needed for sheets, gets set in TransactionsListView
reloadTransactions: $shouldReloadTransactions)
+ .accessibilitySortPriority(1) // Reads third
// } iOS 15 + 16
}
- // Action & Settings buttons
if #available(iOS 26.0, *) {
- let actionsButton = ActionItem(tab: TalerTab.actions,
- onTap: onActionTab,
- onDrag: onActionDrag)
+ // floating Settings & Action buttons
+ let buttons = VStack(alignment: .trailing) {
+ if voiceOverEnabled {
+ SettingsButton26(sortPriority: 0)
+ }
+ Spacer()
+ ActionItem(tab: TalerTab.actions,
+ onTap: onActionTab,
+ onDrag: onActionDrag)
.accessibilitySortPriority(2) // Reads second
.matchedTransitionSource(id: "unique_transition_id", in: wrapper.namespace)
- HStack { // floating bottom right
- Spacer(minLength: 8)
+// .navigationTransition(.zoom(sourceID: "unique_transition_id", in: wrapper.namespace))
+ }
+ .padding(.horizontal)
#if OIM
- if !controller.oimModeActive { // hide for OIM
- actionsButton
- }
+ if !controller.oimModeActive { // hide for OIM
+ buttons
+ }
#else
- actionsButton
+ buttons
#endif
- }
- .padding(.horizontal)
} // iOS 26
}.background(balanceActions)
- .accessibilitySortPriority(1) // Reads third
}.navigationViewStyle(.stack)
.accessibilitySortPriority(3) // Reads first
if #available(iOS 26.0, *) {
- // no TabView, just a single NavigationView. BalancesListView with overlaid Actions button
+ // no TabView, just a single NavigationView, containing the BalancesListView
+ // with floating Liquid Glass Actions button
return balancesStack.id(viewState.rootViewId) // change rootViewId to trigger popToRootView behaviour
} else {
- // TabView with 3 tabs, middle is Actions
+ // iOS 15..18: TabView with 3 tabs, middle is Actions
let settingsStack = NavigationView {
SettingsView(stack: stack.push(),
navTitle: settingsTitle)
@@ -256,7 +262,8 @@ struct WalletMain: View {
}
func tabBarView() -> some View {
- // custom tabBar (with Actions button) is rendered on top of the TabView, and overlaps its tabBar
+ // iOS 15..18: custom tabBar (with Actions button) is rendered on top of the TabView,
+ // and overlays the native iOS tabBar (which is still used for VoiceOver)
TabBarView(selection: tabSelection(),
hidden: $tabBarModel.tabBarHidden,
onActionTab: onActionTab,