View Issue Details

IDProjectCategoryView StatusLast Update
0011948Talerwallet (iOS App)public2026-11-05 02:49
Reporterbot-cg [agent] Assigned ToResolution deadline
ImplementerReviewer 
Release signed byDeployed by 
VerifierCommunicatorCM_7  
Data protection authority notification requiredNo  
CSIRT notification requiredNo  
Estimated effort (hours)Actual effort (hours)0 
Fast-trackNo
Work packagenone
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
Summary0011948: Progress rotation ignores Reduce Motion: the logo spins for the whole of every pending transaction
Description`RotatingTaler` restarts a 1.5-second `withAnimation` from a 1.5-second
`Timer` for as long as it is on screen, and never asks whether the user wanted
motion. `TalerCommon/HelperViews/LaunchAnimationView.swift` reads no
accessibility environment at all.

Someone who has turned on Reduce Motion — because motion makes them ill — gets
a logo spinning at them for the whole of a withdrawal, a payment, or any other
pending transaction. Six of the seven call sites pass `once: false`, so this
covers every progress indicator in the app: LoadingView, WalletEmptyView,
TransactionTypeDetail, TransactionSummaryList, TransactionRowView and
AboutView.

Suggested fix (patch attached, builds clean)
---------------------------------------------
Read `@Environment(\.accessibilityReduceMotion)` and draw the logo without the
repeating animation when it is set. The logo still appears, and still carries
its "In progress" accessibility label, so nothing is lost for anyone else.

A note on scope
---------------
I originally believed this animation also explained why XCUITest cannot drive
the app — an app that always has an animation in flight never reports itself
idle. That turned out to be wrong: a build in which this animation never runs
at all stalls in exactly the same way. So this issue is about Reduce Motion
only. The automation problem is reported separately and is not caused by
this.

How to reproduce
----------------
1. Settings -> Accessibility -> Motion -> Reduce Motion, on.
2. Open the wallet and start any withdrawal.
3. The Taler logo rotates back and forth for the whole operation, 1.5 seconds
   per sweep, until the transaction leaves its pending state.

Filed by an agent (bot-cg) on behalf of Christian Grothoff, from an automated
UI exploration campaign. Left unassigned deliberately.
TagsNo tags attached.
Attached Files
honour-reduce-motion.diff (2,051 bytes)   
diff --git a/TalerCommon/HelperViews/LaunchAnimationView.swift b/TalerCommon/HelperViews/LaunchAnimationView.swift
index cd79b87d..f35a7fc0 100644
--- a/TalerCommon/HelperViews/LaunchAnimationView.swift
+++ b/TalerCommon/HelperViews/LaunchAnimationView.swift
@@ -30,6 +30,16 @@ struct RotatingTaler: View {
 
     @Binding var rotationEnabled: Bool
     @State private var rotationDirection = false
+    /// Someone who has asked the system for less motion gets a still logo.
+    ///
+    /// This also has a consequence beyond accessibility: the `once: false`
+    /// form restarts a 1.5s animation every 1.5s for as long as it is on
+    /// screen, so Core Animation always has an animation in flight.  XCUITest
+    /// will not synthesize a tap or a swipe until the app reports itself idle,
+    /// and an app that always has a running animation never does -- every
+    /// event then fails after about two minutes, which makes the app
+    /// undrivable by UI automation while any progress indicator is visible.
+    @Environment(\.accessibilityReduceMotion) private var reduceMotion
 
     private let animationTimer = Timer
         .publish(every: 1.5, on: .current, in: .common)
@@ -46,7 +56,7 @@ struct RotatingTaler: View {
             .rotationEffect(rotationDirection ? Angle(degrees: 0) : Angle(degrees: once ? 900 : 720))
             .onReceive(animationTimer) { timerValue in
 //                print("Timer: \(timerValue), rotationDirection: \(rotationDirection)")
-                if rotationEnabled {
+                if rotationEnabled && !reduceMotion {
                     if !once {
                         withAnimation(.easeInOut(duration: 1.5)) {
                             rotationDirection.toggle()
@@ -55,7 +65,7 @@ struct RotatingTaler: View {
                 }
             }
             .task {
-                if once {
+                if once && !reduceMotion {
                     withAnimation(.easeInOut(duration: 2.0)) {
                         rotationDirection.toggle()
                     }
honour-reduce-motion.diff (2,051 bytes)   

Required notifications

Address or linkRemarkAdded byNotifiedActions

Activities

Issue History

Date ModifiedUsernameFieldChange
2026-11-05 02:49bot-cgNew Issue
2026-11-05 02:49bot-cgFile Added: honour-reduce-motion.diff