Repository navigation
feat(mobile): Material 3 selection controls on Android - #2342
Conversation
Segmented, PercentageSlider and Chip gain Android variants built on @expo/ui Jetpack Compose (segmented buttons, Slider, FilterChip). The Activity options sheet uses a Compose RadioButton through a new RadioMark primitive, and its unread-only switch moves to the Toggle primitive. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 23 minutes. View limit details
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T
Compose reads each SegmentedButton's position from its parent once, so a row that grows from two to four options (Usage, once longer periods are supported) kept the two-item shapes: the selected middle segment drew a rounded end. Key the row on its option values. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T
Captured on an Android 16 x86_64 emulator with gesture navigation and a debug dev client built from main plus #2342 and #2343. Light and dark at the default font, plus the largest font (2.0) for Usage and the Activity filter. Computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Captured on an Android 16 x86_64 emulator with gesture navigation and a debug dev client built from main plus #2342 and #2343. Before/after pairs for Back on the attach tray, full client and drawer, and held back swipes. Computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-m7tsnr # Conflicts: # apps/mobile/DESIGN.md # apps/mobile/src/ui/shell/activity-filters.tsx
…to claude/project-thread-m7tsnr
…parent container Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T
…uses the list shape Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T
Recaptured on an Android 16 x86_64 emulator with gesture navigation and a fresh debug dev client built from main 55c8848 plus #2342 and #2343. Light and dark at the default font, plus the radio-list fallback at the largest font (2.0). Computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Recaptured on an Android 16 x86_64 emulator with gesture navigation and a fresh debug dev client built from main 55c8848 plus #2342 and #2343. Back on the attach tray, full client and drawer; the predictive-swipe frames are dropped with the claim. Computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-m7tsnr # Conflicts: # apps/mobile/DESIGN.md # apps/mobile/src/ui/primitives/segmented.android.tsx
There was a problem hiding this comment.
🔇 Additional comments: 10
apps/mobile/src/ui/primitives/radio-mark.tsx: 1
1-32: LGTM!apps/mobile/src/ui/primitives/radio-mark.android.tsx: 1
1-18: LGTM!apps/mobile/src/ui/primitives/chip.android.tsx: 1
1-39: LGTM!apps/mobile/src/ui/primitives/percentage-slider.android.tsx: 1
1-55: LGTM!apps/mobile/test/material-controls-android.test.tsx: 1
1-195: LGTM!apps/mobile/DESIGN.md: 1
47-47: LGTM!Also applies to: 288-288
changes/2342-android-material-controls.md: 1
1-11: LGTM!apps/mobile/src/ui/primitives/segmented.android.tsx: 2
48-85: LGTM!Also applies to: 87-117
40-47: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
⚠️ Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.Measure the container width, not the window width.
segmentsFitreceivesuseWindowDimensions().widthand subtracts a fixedspace.x5 * 2margin. The real space for aSegmentedis its parent's width. A drawer page, aSheetStackpage or a padded settings group can have more thanspace.x5of horizontal inset. In that case the estimate says the labels fit, but they do not. The row then renders, andComposeText maxLines={1} softWrap={false}clips the labels.DESIGN.mdLine 288 says this must never happen.
labelWidthuses 0.5 em per Latin character. That value is close to the Roboto average, so wide labels have little error margin. A container measurement removes the inset error, which is the larger one.Read the width from
onLayouton the outerView. Fall back to the window width until the first layout arrives.♻️ Proposed change
- const { width, fontScale } = useWindowDimensions(); + const window = useWindowDimensions(); + const [measured, setMeasured] = useState<number | undefined>(); + const width = measured ?? window.width - space.x5 * 2; + const fontScale = window.fontScale;Change
segmentsFitso that it compares againstwidthdirectly, without subtractingspace.x5 * 2. AddonLayout={(e) => setMeasured(e.nativeEvent.layout.width)}to the outerViewin both branches.apps/mobile/src/ui/shell/activity-filters.tsx: 1
2-12: LGTM!Also applies to: 70-70, 205-209
ℹ️ Review info
⚙️ Run configuration
- Configuration used: defaults
- Review profile: CHILL
- Plan: Advanced
- Run ID:
37c8ec7f-39df-484c-9a37-e2d16d54c0a0
⛔ Files ignored due to path filters: 16
.github/pr-screenshots/2342-android-material/activity-filter-dark.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/activity-filter-font200-light.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/activity-filter-light.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/activity-unread-off-dark.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/activity-unread-on-light.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/bg-slider-dragged-dark.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/bg-slider-dragged-light.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/bg-top-dark.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/bg-top-light.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/look-chips-dark.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/look-chips-light.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/routine-chips-dark.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/routine-chips-light.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/usage-dark.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/usage-font200-light.pngis excluded by!**/*.png.github/pr-screenshots/2342-android-material/usage-light.pngis excluded by!**/*.png
📒 Files selected for processing: 9
apps/mobile/DESIGN.mdapps/mobile/src/ui/primitives/chip.android.tsxapps/mobile/src/ui/primitives/percentage-slider.android.tsxapps/mobile/src/ui/primitives/radio-mark.android.tsxapps/mobile/src/ui/primitives/radio-mark.tsxapps/mobile/src/ui/primitives/segmented.android.tsxapps/mobile/src/ui/shell/activity-filters.tsxapps/mobile/test/material-controls-android.test.tsxchanges/2342-android-material-controls.md
Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.
Captured on a Xiaomi phone (Android 16, HyperOS 3, arm64, gesture navigation) running a debug dev client of main 55c8848 plus #2342 and #2343, in the zh-CN locale. Light and dark, plus the radio-list fallback at font scale 2.0. Status bar and computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Captured on a Xiaomi phone (Android 16, HyperOS 3, arm64, gesture navigation) running a debug dev client of main 55c8848 plus #2342 and #2343, in the zh-CN locale. 2c shows Back leaving the full client although the WebView had a history entry. Status bar and computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T
* feat(mobile): Android predictive back and Back for in-screen surfaces Turn on android.predictiveBackGestureEnabled. Back closes the composer's attach tray through Expo Router's usePreventRemove, and the full client steps back through its web history before leaving; the floating exit still leaves at once. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T * chore(mobile): name the #2343 change fragment Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T * docs(mobile): add Android emulator screenshots for #2343 Captured on an Android 16 x86_64 emulator with gesture navigation and a debug dev client built from main plus #2342 and #2343. Before/after pairs for Back on the attach tray, full client and drawer, and held back swipes. Computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mobile): recapture Android emulator screenshots for #2343 Recaptured on an Android 16 x86_64 emulator with gesture navigation and a fresh debug dev client built from main 55c8848 plus #2342 and #2343. Back on the attach tray, full client and drawer; the predictive-swipe frames are dropped with the claim. Computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mobile): add physical-phone screenshots for #2343 Captured on a Xiaomi phone (Android 16, HyperOS 3, arm64, gesture navigation) running a debug dev client of main 55c8848 plus #2342 and #2343, in the zh-CN locale. 2c shows Back leaving the full client although the WebView had a history entry. Status bar and computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
The radio-list fallback for segmented rows from #2342 now uses the segmented-list corners and gap from this PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbudM552v4qrLMzopz7j7k
) * feat(mobile): use Material 3 selection controls on Android Segmented, PercentageSlider and Chip gain Android variants built on @expo/ui Jetpack Compose (segmented buttons, Slider, FilterChip). The Activity options sheet uses a Compose RadioButton through a new RadioMark primitive, and its unread-only switch moves to the Toggle primitive. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T * chore(mobile): add #2342 change fragment and drop prohibited comments Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T * fix(mobile): rebuild Android segmented row when its options change Compose reads each SegmentedButton's position from its parent once, so a row that grows from two to four options (Usage, once longer periods are supported) kept the two-item shapes: the selected middle segment drew a rounded end. Key the row on its option values. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T * feat(mobile): use Material 3 segmented lists for Android grouped rows Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T * chore(mobile): add #2348 change fragment Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T * fix(mobile): fall back to a radio list when Android segments cannot fit Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T * docs(mobile): add Android emulator screenshots for #2342 Captured on an Android 16 x86_64 emulator with gesture navigation and a debug dev client built from main plus #2342 and #2343. Light and dark at the default font, plus the largest font (2.0) for Usage and the Activity filter. Computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mobile): outline unselected Android segments and chips on a transparent container Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T * fix(mobile): keep segmentCorners in a pure design module so the root typecheck stays on Node timers Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T * docs(mobile): recapture Android emulator screenshots for #2342 Recaptured on an Android 16 x86_64 emulator with gesture navigation and a fresh debug dev client built from main 55c8848 plus #2342 and #2343. Light and dark at the default font, plus the radio-list fallback at the largest font (2.0). Computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mobile): add physical-phone screenshots for #2348 Captured on a Xiaomi phone (Android 16, HyperOS 3, arm64, gesture navigation, zh-CN) running an arm64 debug dev client of main 48f35fb with this branch's JavaScript, in light, dark and font scale 2.0. Settings groups and the Activity filter sheet: gaps between rows, large outer and small inner corners. Status bar and computer names are redacted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Requested by Vain · project thread
Why the change
On Android the app drew its own segmented control, slider, chips and radio dots, and used an AppCompat switch, even though Material 3 has native components for all of them.
DESIGN.md→ Native first and theandroid-materialskill (#2339, #2338) say to use them.Refs: the first focused PR from the Android Material 3 audit in the project thread. Main is merged in, including #2349's native segmented controls.
Special things to note
@expo/ui58's Jetpack Compose components already ship in the installed build, and no native inputs change. Each new*.android.tsxcan be deleted on its own to revert that control.segmented.android.tsx. This branch keeps its version, which adds the remount fix, the large-text radio fallback and the transparent unselected container, and now takesSegmentedPropsfromsegmented.tsx.segmented-native.test.tsxfrom main passes against it.Segmentedbecomes a radio list of the same options.pnpm lint, mobile and roottsc,oxfmtandvitest apps/mobile/test(119 files passed, 7 skipped), includingmaterial-controls-android.test.tsxandsegmented-native.test.tsx.Change outline
Segmenteduses ComposeSingleChoiceSegmentedButtonRow, with a radio-list fallback at large text.PercentageSlideruses ComposeSlider.ChipusesFilterChip.RadioButtonthroughRadioMark.Toggle, which is ComposeSwitchon Android.DESIGN.md: the primitives list and the Activity options rule name the Android components and the fallback.Physical phone (Xiaomi, Android 16; status bar and computer name redacted). All 16 captures are in
.github/pr-screenshots/2342-android-material/phone/.2342-android-material/phone/usage-light.png2342-android-material/phone/usage-dark.png2342-android-material/phone/usage-font200-light.png2342-android-material/phone/activity-filter-dark.png2342-android-material/phone/bg-slider-dragged-dark.png2342-android-material/phone/look-chips-dark.png2342-android-material/phone/routine-chips-light.png2342-android-material/phone/activity-filter-font200-light.pngAndroid 16 emulator:
2342-android-material/usage-light.png2342-android-material/usage-dark.png2342-android-material/activity-filter-light.png2342-android-material/activity-filter-dark.png2342-android-material/activity-unread-on-light.png2342-android-material/activity-unread-off-dark.png2342-android-material/bg-top-light.png2342-android-material/bg-top-dark.png2342-android-material/bg-slider-dragged-light.png2342-android-material/bg-slider-dragged-dark.png2342-android-material/look-chips-light.png2342-android-material/look-chips-dark.png2342-android-material/routine-chips-light.png2342-android-material/routine-chips-dark.pnghttps://claude.ai/code/session_01SJ8gcMTgoRjSRBNFswZT9T
Generated by Claude Code