Skip to content

Native Material 3 settings screen for Android, plus fixes from a codebase review - #133

Merged
mmazzarolo merged 9 commits into
masterfrom
claude/codebase-review-ab7aa5
Aug 2, 2026
Merged

Native Material 3 settings screen for Android, plus fixes from a codebase review#133
mmazzarolo merged 9 commits into
masterfrom
claude/codebase-review-ab7aa5

Conversation

@mmazzarolo

@mmazzarolo mmazzarolo commented Aug 2, 2026

Copy link
Copy Markdown
Owner

What changed

Bug fixes from a codebase review

  • Exercise loop: the session store echoed each step change back into the fragment's initialStepIndex prop, tearing down and recreating the animation loop at every step boundary. The mount-time index now lives in a ref.
  • Audio service: guided-breathing sources were prepared in a filtered array and read back by position, which silently broke for any mode without step cues. Sources now map by key; the disabled mode keeps only the ending bell (new unit test).
  • Pressable: all wrapper handlers now sit after the props spread, so a caller-supplied onPressIn can no longer replace the haptic handler.

Android settings screen rebuilt with Jetpack Compose Material 3

Native Compose views via @expo/ui (production-ready in SDK 56), aligned with the stock Android settings app and verified against it on a Pixel emulator — window/card tokens are pixel-identical (surfaceContainer background, surfaceBright cards, 20dp/5dp grouped card corners, 2dp gaps):

  • stock-style title bar (tonal circular back button, large title); the native-stack header stays hidden on Android only
  • rows are Material ListItems with ripple; switch rows toggle on a tap anywhere in the row
  • Voice keeps the list-preference radio dialog; the two-option Theme choice is an inline segmented button row
  • Material You dynamic color on Android 12+, driven by the app theme so the in-app light/dark override wins over the system
  • the previous JS implementation moved to settings-ui.web.tsx, so web keeps working; iOS is untouched

iOS fixes

  • stepper value no longer truncates at two digits ("10" rendered as "1..")
  • disabled preset rows dim immediately (dynamic opacity on TouchableOpacity is ignored by its internal animation; the dimming now lives on an inner view)

Dependencies

  • @expo/ui ~56.0.24 added; expo bumped to 56.0.18 within its existing range (needs the newer expo-modules-core)

mmazzarolo and others added 9 commits August 1, 2026 07:59
The session store echoes each step change back into the fragment's
initialStepIndex prop, so keeping it in the loop effect's dependencies
tore down and recreated the animation loop at every step boundary.
Capture the mount-time index in a ref instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
setupGuidedBreathingAudio filtered null sources out of an array and then
read players back by index, which silently broke for any mode without
step cues (e.g. "disabled"). Prepare each source by name and only create
the step players when all step cues exist; the ending bell is always
created. Add a unit test for the disabled mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
onPressIn sat before the {...otherProps} spread, so a caller-supplied
onPressIn would silently replace the haptic handler while onLongPress
and onPressOut sat after it. Move all three wrapper handlers after the
spread; each one already delegates to the caller's handler.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@expo/ui provides the native Jetpack Compose components for the new
Android settings screen. expo moves to the 56.0.18 patch (same ~56.0.x
range) because @expo/ui 56.0.24 needs the ColorCompat class introduced
in a newer expo-modules-core.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stepper value container was too narrow for two digits and rendered
"10" as "1..". Disabled dimming sat on TouchableOpacity, which manages
its own animated opacity and ignores dynamic opacity style changes, so
enabling the custom pattern did not gray out the presets until an
unrelated re-render. The dimming now lives on an inner View, and the
stepper icons dim through their own style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…al 3

The Android settings UI now renders native Compose Material 3 views via
@expo/ui, aligned with the stock Android settings app (verified against
it on a Pixel emulator, including pixel-exact window/card tokens:
surfaceContainer background, surfaceBright cards, 20dp/5dp grouped card
corners):

- stock-style title bar with a tonal circular back button (native-stack
  header hidden on Android only)
- rows are Material ListItems with ripple; switch rows toggle on row tap
- voice keeps the list-preference radio dialog; the two-option theme
  choice is an inline segmented button row
- the exercise timer is a native slider (new clamped setTimeLimit store
  action); custom pattern durations keep precise stepper buttons
- Material You dynamic color on Android 12+, driven by the app theme so
  the in-app light/dark override wins over the system

The previous JS implementation moves to settings-ui.web.tsx so web keeps
working. Maestro flows updated: dialog/segmented navigation, scroll
steps for the taller Material rows, and a platform-branched timer step
(slider drag on Android, steppers on iOS).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bounded ripple under a rounded clip flashed as a square layer; the
back button now uses a borderless circular ripple with an explicit
radius and a translucent scheme-aware color. The exercise timer goes
back to the +/- stepper buttons; the slider, its store setter, and the
platform-branched e2e steps are removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- web: build the section class list with a ternary instead of leaking a
  literal "false" into className
- android: align section labels with the card titles (32dp) as stock does
- settings-ui.d.ts: real component and prop declarations instead of the
  unresolvable ones skipLibCheck was hiding; widen the iOS icon prop and
  type the two picker call sites the new declarations flagged

Note: keeping the switches/radios interactive inside their rows is
deliberate; a passive control through @expo/ui still consumes taps
natively and creates a dead zone on the control itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e stub

The base variant must keep the same signature as the Android variant, so
the parameter stays and gets an explicit eslint suppression. Caught by
CI; the local lint invocations had piped the exit code away.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mmazzarolo
mmazzarolo merged commit ae63229 into master Aug 2, 2026
1 check passed
@mmazzarolo
mmazzarolo deleted the claude/codebase-review-ab7aa5 branch August 2, 2026 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant