taler-ios

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

commit aaf5850aece1529a12d355b8876cd842f69ce3f1
parent 3ca95c182906b79a354d01bfa9dcc330e26e2889
Author: Marc Stibane <marc@taler.net>
Date:   Wed, 24 Jun 2026 08:21:44 +0200

fix a11y for SettingsButton

Diffstat:
MTalerWallet1/Views/Balances/BalancesListView.swift | 15++++-----------
MTalerWallet1/Views/HelperViews/Buttons.swift | 18++++++++++++++++++
MTalerWallet1/Views/Main/WalletEmptyView.swift | 40+++++++++++++++++++++++-----------------
MTalerWallet1/Views/Main/WalletMain.swift | 41++++++++++++++++++++++++-----------------
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,