View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0011948 | Taler | wallet (iOS App) | public | 2026-11-05 02:49 | 2026-11-05 02:49 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | Reviewer | ||||
| Release signed by | Deployed by | ||||
| Verifier | Communicator | CM_7 | |||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | minor | Reproducibility | always |
| Status | new | Resolution | open | ||
| Summary | 0011948: 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. | ||||
| Tags | No 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()
}
| ||||