Bind ModalBottomSheet, BottomSheetScaffold, date/time pickers, and Tooltip#9
Merged
jonathanpeppers merged 4 commits intoJun 3, 2026
Merged
Conversation
94cb662 to
4c3e205
Compare
4c3e205 to
c951c4d
Compare
There was a problem hiding this comment.
Pull request overview
This PR expands the ComposeNet Material 3 facade to cover additional “dialogs and sheets” UI primitives (sheets, picker dialogs, pickers, and tooltip), using the existing slot-property + $default bitmask pattern and adding the necessary JNI bridges where bindings are stripped/incomplete.
Changes:
- Added new public facades in
Composables.csforModalBottomSheet,BottomSheetScaffold,DatePickerDialog/DatePicker,TimePickerDialog/TimePicker, andTooltip(TooltipBox wrapper). - Added new declarative
[assembly: ComposeDefaults(...)]entries for the newly bridged composables’$defaultmasks. - Added multiple new raw-JNI bridge methods in
ComposeBridges.cs(composables + remember* state holders) and updated the sample to exercise the new UI.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/ComposeNet.Sample/MainActivity.cs | Updates the sample UI to open/dismiss the newly added dialogs/sheets/pickers and demonstrate tooltip behavior. |
| src/ComposeNet.Compose/ComposeDefaults.cs | Adds new declarative ComposeDefaults definitions for the new composables’ $default bitmasks. |
| src/ComposeNet.Compose/ComposeBridges.cs | Introduces new JNI bridge entry points for stripped composables and state-holder remember* helpers. |
| src/ComposeNet.Compose/Composables.cs | Adds the new public facade node types and wires them to the bridges/defaults pattern. |
c951c4d to
f63f797
Compare
jonathanpeppers
added a commit
that referenced
this pull request
Jun 3, 2026
…ckerState PR #9 review (#9 (review)): 1. Generate a $default enum for DatePickerKt.DatePicker via the declarative ComposeDefaults attribute, replacing the hard-coded 0b11111110 magic number in ComposeBridges.DatePicker. The bridge now takes an `int defaults` parameter the same way every other composable bridge does, and DatePicker.Render passes (int)DatePickerDefault.All. 2. Expose DatePickerState/TimePickerState wrappers so callers can read the picked date/time from a button callback. Each new wrapper holds the JVM state interface (IDatePickerState / ITimePickerState — both bound, no extra JNI needed) and exposes Kotlin-faithful properties: DatePickerState.SelectedDateMillis long? (Unix epoch UTC) DatePickerState.DisplayedMonthMillis long TimePickerState.Hour int TimePickerState.Minute int TimePickerState.Is24Hour bool DatePicker / TimePicker now accept an optional state argument; if omitted (or for TimePicker, with default initial values), a fresh state is created internally and the selection is unobservable, just like the previous behaviour. Sample (MainActivity.cs) demonstrates the read-back pattern: tapping the time picker dialog's OK button now displays "09:30" (the initial values) in the trigger row's status text, and the date picker's confirm callback reads SelectedDateMillis from its bound state. Verified on emulator-5554: - dotnet build src/ComposeNet.Compose — clean (generator emits DatePickerDefault correctly). - Sample installs and launches with no FATAL/AndroidRuntime errors. - UI-automation drive: open date dialog, OK -> "Picked date: (none)" (null SelectedDateMillis when nothing selected, no crash). Open time dialog, OK -> "Picked time: 09:30" (reads initial values via the bound ITimePickerState). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…oltip Follow-up to PR #6 — completes the user-facing pieces of issue #3 by binding the remaining Material 3 dialogs, sheets, pickers, and the Tooltip composable. Mirrors PR #6's named-slot facade + $default bitmask pattern, with raw JNI bridges for any composable or remember*State builder stripped from the binding. Composables added (in src/ComposeNet.Compose/Composables.cs): - ModalBottomSheet (state via direct C# call to RememberModalBottomSheetState) - BottomSheetScaffold (state via direct C# call to RememberBottomSheetScaffoldState) - DatePickerDialog + DatePicker (state via JNI rememberDatePickerState-EU0dCGE) - TimePicker + TimePickerDialog (state via JNI rememberTimePickerState) - Tooltip (TooltipBox) with TooltipDefaults.rememberPlainTooltipPositionProvider Issue #3 also listed BasicTooltipBox and BasicEdgeToEdgeDialog. Both live in androidx.compose.material3.internal — they are foundation primitives that the public Tooltip / Dialog / AlertDialog wrappers are built on top of, and there is no user-facing scenario where they're preferable to the public composables we already bind. Skipped on purpose, with comments in Composables.cs explaining why. JNI plumbing: 7 composable bridges + 4 state-holder/helper bridges in ComposeBridges.cs, with full mangled descriptors documented inline so future readers can verify against the AAR via javap. Each facade has a corresponding [Flags] $default enum generated via the new declarative `[assembly: ComposeDefaults("FooDefault", ...)]` form introduced in PR #8 — names prefixed with `!` consume a bit position but emit no enum member, covering the params the caller always provides. Sample (src/ComposeNet.Sample/MainActivity.cs) gains a button row that toggles each new dialog/sheet via MutableState<bool>, plus an inline Tooltip anchor so the popup can be exercised by long-pressing on the emulator. Build + smoke-tested on emulator-5554: app launches cleanly, no NoSuchMethodError / UnsatisfiedLinkError / NPE in the app pid. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ckerState PR #9 review (#9 (review)): 1. Generate a $default enum for DatePickerKt.DatePicker via the declarative ComposeDefaults attribute, replacing the hard-coded 0b11111110 magic number in ComposeBridges.DatePicker. The bridge now takes an `int defaults` parameter the same way every other composable bridge does, and DatePicker.Render passes (int)DatePickerDefault.All. 2. Expose DatePickerState/TimePickerState wrappers so callers can read the picked date/time from a button callback. Each new wrapper holds the JVM state interface (IDatePickerState / ITimePickerState — both bound, no extra JNI needed) and exposes Kotlin-faithful properties: DatePickerState.SelectedDateMillis long? (Unix epoch UTC) DatePickerState.DisplayedMonthMillis long TimePickerState.Hour int TimePickerState.Minute int TimePickerState.Is24Hour bool DatePicker / TimePicker now accept an optional state argument; if omitted (or for TimePicker, with default initial values), a fresh state is created internally and the selection is unobservable, just like the previous behaviour. Sample (MainActivity.cs) demonstrates the read-back pattern: tapping the time picker dialog's OK button now displays "09:30" (the initial values) in the trigger row's status text, and the date picker's confirm callback reads SelectedDateMillis from its bound state. Verified on emulator-5554: - dotnet build src/ComposeNet.Compose — clean (generator emits DatePickerDefault correctly). - Sample installs and launches with no FATAL/AndroidRuntime errors. - UI-automation drive: open date dialog, OK -> "Picked date: (none)" (null SelectedDateMillis when nothing selected, no crash). Open time dialog, OK -> "Picked time: 09:30" (reads initial values via the bound ITimePickerState). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirror the pattern from PR #11 (bfb108d) — wrap each JNIEnv.CallStatic*Method invocation in try/finally and GC.KeepAlive every managed wrapper whose .Handle was read into a JValue. Once the handle is in the JValue the JIT can consider the wrapper dead, and a GC mid-JNI-call would finalize it and invalidate the handle. Applied to: ModalBottomSheet, BottomSheetScaffold, DatePickerDialog, DatePicker, TimePicker, TimePickerDialog, TooltipBox, RememberDatePickerState, RememberTimePickerState, RememberTooltipState, RememberPlainTooltipPositionProvider. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3cf9069 to
f22c203
Compare
This was referenced Jun 3, 2026
The sample had grown past one screen — buttons, chips, FAB, dialog triggers, picker triggers and a tooltip were all stacked in one Column. Split them across three tabs driven by the existing 'tab' MutableNumberState: - Basics: Text, Button, IconButton, OutlinedTextField, Card - Buttons: chips, Tooltip, FloatingActionButton - Pickers: ModalBottomSheet/DatePicker/TimePicker triggers + readouts NavigationBar at the bottom of the Column switches tabs. Overlays (AlertDialog, ModalBottomSheet, DatePickerDialog, TimePickerDialog) are still rendered at the root regardless of tab so a dialog opened from the FAB on the Buttons tab still works after switching. Note: the NavigationBar isn't pinned to the bottom edge — that needs Scaffold (issue #12). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #6 — completes the user-facing pieces of #3 by binding the remaining Material 3 dialogs, sheets, pickers, and
Tooltip. Rebased onto currentmain, which picks up the declarative[assembly: ComposeDefaults(...)]source generator from #8 and uses it for every new$defaultenum.Composables added
ModalBottomSheetModalBottomSheet_androidKtSheetStatevia direct C# call toModalBottomSheetKt.RememberModalBottomSheetState(NOT stripped)BottomSheetScaffoldBottomSheetScaffoldKtBottomSheetScaffoldStatevia direct C# call toBottomSheetScaffoldKt.RememberBottomSheetScaffoldState(NOT stripped)DatePickerDialogDatePickerDialog_androidKtDatePickerStatevia JNIrememberDatePickerState-EU0dCGEDatePickerDatePickerKtDatePickerState)TimePickerTimePickerKtTimePickerStatevia JNIrememberTimePickerStateTimePickerDialogTimePickerDialogKtTimePickerState)Tooltip(TooltipBox)TooltipKtTooltipStatevia JNIrememberTooltipState+TooltipDefaults.INSTANCE.rememberPlainTooltipPositionProvider-kHDZbjcRememberModalBottomSheetStateandRememberBottomSheetScaffoldStateare present in the bound assembly, so the facades call them directly through C# instead of going through JNI — per the upstream binding shape noted in #6's description.Intentionally NOT bound
#3 also listed
BasicTooltipBoxandBasicEdgeToEdgeDialog. Both live inandroidx.compose.material3.internal— they're the foundation primitives that the publicTooltip/Dialog/AlertDialog/DatePickerDialog/TimePickerDialog/TooltipBoxwrappers are built on top of, and there is no user-facing scenario where they're preferable to the public composables we already bind. (Confirmed empirically:BasicEdgeToEdgeDialogrendered as a fullscreen overlay with no way to dismiss it from the sample.) Skipped on purpose, with comments inComposables.csexplaining why.Pattern
Each facade mirrors
AlertDialogfrom #6 — slot composables exposed as named properties on the facade, optional slots tracked via the standard Compose$defaultbitmask. The 6 new bitmask enums are generated via the declarative attribute pattern from #8:!paramconsumes a bit position without emitting an enum member, covering the params the caller always provides.JNI plumbing lives in
ComposeBridges.cs: 7 composable bridges + 4 state-holder/helper bridges, with full mangled descriptors documented inline so future readers can verify against the AAR viajavap.Sample usage
BottomSheetScaffoldis bound but not exercised in the sample — the simplerModalBottomSheetcovers the smoke-test path; the scaffold variant is available for callers that need a persistent sheet.Verification
dotnet build src/ComposeNet.Compose/ComposeNet.Compose.csproj— clean (the source generator from Generate ComposeDefaults enums via declarative attribute #8 emits the 6 new enums correctly).dotnet build src/ComposeNet.Sample/ComposeNet.Sample.csproj -t:Install— clean.emulator-5554, noFATAL/AndroidRuntimeerrors in the app pid; each new dialog/sheet/picker can be opened and dismissed from the sample's button row.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com