Skip to content

Android: add the in-app "Reduce motion in NOOP" toggle as a third rememberPoseStill() signal #941

Closed

Description

@vishk23

Follow-up to #931, which adds a third motion signal on the Apple side only. Filed because #931's description originally claimed this Kotlin pref already existed — it does not, and that wording is corrected there.

The asymmetry

After #931 lands, the two gates read differently:

signals
Apple — NoopMotionState.poseStill (Packages/StrandDesign/Sources/StrandDesign/NoopMotion.swift:140) system Reduce Motion ‖ Low Power Mode ‖ in-app "Reduce motion in NOOP"
Android — rememberPoseStill() (android/app/src/main/java/com/noop/ui/NoopMotion.kt:149) system Reduce Motion ‖ battery saver
fun rememberPoseStill(): Boolean = rememberReduceMotion() || rememberPowerSaveMode()

The first two signals reached parity in #909 / #911. The third — an in-app toggle, default OFF — is the one that is Apple-only, and it is the signal the user who asked for this actually wanted: a way to quiet the app without putting the whole phone into battery saver.

What the Android side needs

  1. A pref. NoopPrefs (android/app/src/main/java/com/noop/ui/MainActivity.kt:163) stores keys as KEY_* constants, so this should be const val KEY_QUIET_MOTION = "noop.quietMotion" — note the key string is the cross-platform contract and must match the Apple @AppStorage key noop.quietMotion exactly, so a .noopbak round-trip carries the setting across platforms. (Worth saying plainly: the correct Kotlin spelling is NoopPrefs.KEY_QUIET_MOTION, not NoopPrefs.quietMotion — the latter is the name Make Low Power Mode mean "stop everything decorative", and stop the tilt sensor (#909) #931 wrongly claimed already existed, and it does not match this file's convention either.)
  2. A third term in the gate, read live rather than once — the Apple side uses UserDefaults.didChangeNotification so flipping the toggle poses the surfaces without leaving the screen; Android needs the SharedPreferences equivalent, for the same reason ACTION_POWER_SAVE_MODE_CHANGED is already observed.
  3. A Settings toggle, default OFF, under the Appearance section, with the string translated into the catalog locales.
  4. Update PoseStillCoverageTest. android/app/src/test/java/com/noop/ui/PoseStillCoverageTest.kt:112 currently pins the exact two-signal source line:
    code.contains("fun rememberPoseStill(): Boolean = rememberReduceMotion() || rememberPowerSaveMode()")
    That assertion will fail the moment the third signal is added, which is the test working correctly — it is a census of the gate's shape, and the shape is changing. The Apple twin (QuietMotionCoverageTests.testGateReadsAllThreeSignalsAndStaysLive) is the model for what it should assert afterwards: that all three signals are OR-ed and that each stays live, because losing one signal is invisible in the app — the screen looks right in whichever mode still works.

Not in scope here

The LiquidMotion half of #931 (stopping the decorative 60 Hz CMMotionManager feed at the app boundary) has no Android twin to fix: there is no equivalent decorative sensor loop on that side. This issue is only the third gate signal and its toggle.

Activity

  1. added a commit that references this issue on Aug 8, 2026
    789a180
  2. added a commit that references this issue on Aug 17, 2026
    92200e2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions