Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,20 @@ so `[CallerFilePath]` + `[CallerLineNumber]` slot keys inside
Kotlin ctor needs JNI (mangled because `selection: TextRange` is a
`@JvmInline value class`); everything else (`Text`/`Selection`/`Copy(…)`)
is exposed by the runtime binding. See issue #204.
- `Layout` exposes a low-level measure-and-place primitive: ctor takes a
user `Func<MeasureScope, IReadOnlyList<Measurable>, Constraints, MeasureResult>`
delegate, the `MeasurePolicy` parameter is built once via a tiny Java
helper that returns a SAM lambda — `MeasurePolicy` is a Kotlin
`fun interface`, so `javac` resolves the single abstract member
(`measure-3p2s80s`, mangled because `Constraints` is `@JvmInline value class`)
by signature via `LambdaMetafactory` and the source never has to spell the
illegal `-` identifier. Default interface methods (the four
`IntrinsicMeasureScope.*Intrinsic*` helpers) are inherited correctly by the
synthesized class. The SAM instance + JCW lambda are cached via
`composer.Remember` so JNI identity stays stable across recompositions.
None of (custom user-delegate ctor, wrapper-typed params not in
`ComposeValueTypes`, JCW with mutable `Body`) fit any `[ComposeFacade]`
phase. See issue #144.

Applying `[ComposeFacade]` to an unsupported bridge emits CN3002 (unsupported
param), CN3003 (scope misuse), CN3005 (invalid callback type), CN3006 (slot
Expand Down
63 changes: 33 additions & 30 deletions docs/api-coverage.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Jetpack Compose ⇄ Microsoft.AndroidX.Compose API coverage

Generated by `scripts/api-comparison.cs` on 2026-06-11 21:52 UTC.
Generated by `scripts/api-comparison.cs` on 2026-06-12 22:41 UTC.

Source of truth: AndroidX `-sources.jar` files for the Compose artifact
versions this repo references (see `Directory.Build.targets`). Re-run after
Expand All @@ -9,25 +9,25 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
## Summary

- **Total Kotlin public symbols in scope**: 2215
- **Covered by Microsoft.AndroidX.Compose (any kind)**: 362 (**16.3%**)
- **Covered by Microsoft.AndroidX.Compose (any kind)**: 375 (**16.9%**)

- **@Composable functions**: 159 / 287 (**55.4%**)
- **@Composable functions**: 160 / 287 (**55.7%**)

### Per-module coverage

| Module | Composables | All public |
| --- | --- | --- |
| `animation` | 8/17 (47%) | 16/56 (29%) |
| `animation-core` | 0/25 (0%) | 0/161 (0%) |
| `foundation` | 15/42 (36%) | 45/356 (13%) |
| `foundation` | 15/42 (36%) | 48/356 (13%) |
| `foundation-layout` | 7/11 (64%) | 43/128 (34%) |
| `material3` | 109/133 (82%) | 168/316 (53%) |
| `runtime` | 6/20 (30%) | 13/200 (7%) |
| `runtime-saveable` | 1/3 (33%) | 1/13 (8%) |
| `ui` | 10/26 (38%) | 44/596 (7%) |
| `ui` | 11/26 (42%) | 51/596 (9%) |
| `ui-graphics` | 0/0 (0%) | 10/187 (5%) |
| `ui-text` | 0/0 (0%) | 13/129 (10%) |
| `ui-unit` | 0/0 (0%) | 5/49 (10%) |
| `ui-text` | 0/0 (0%) | 14/129 (11%) |
| `ui-unit` | 0/0 (0%) | 7/49 (14%) |
| `activity-compose` | 1/5 (20%) | 2/11 (18%) |
| `navigation-compose` | 2/5 (40%) | 2/13 (15%) |

Expand Down Expand Up @@ -344,7 +344,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [x] `SelectionContainer`(2) → type match
- [x] `VerticalPager`(16) → type match

### Other top-level functions — 22/156 (14%)
### Other top-level functions — 25/156 (16%)

- [ ] `AbsoluteCutCornerShape`(5)
- [ ] `AbsoluteRoundedCornerShape`(5)
Expand Down Expand Up @@ -398,11 +398,11 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [ ] `CornerSize`(1)
- [ ] `CutCornerShape`(5)
- [x] `TextFieldBuffer.delete`(2) → TextFieldBuffer.* extension
- [ ] `PointerInputScope.detectDragGestures`(7)
- [x] `PointerInputScope.detectDragGestures`(7) → PointerInputScope.* extension
- [ ] `PointerInputScope.detectDragGesturesAfterLongPress`(5)
- [ ] `PointerInputScope.detectHorizontalDragGestures`(5)
- [x] `PointerInputScope.detectTapGestures`(5) → PointerInputScope.* extension
- [ ] `PointerInputScope.detectTransformGestures`(3)
- [x] `PointerInputScope.detectTransformGestures`(3) → PointerInputScope.* extension
- [ ] `PointerInputScope.detectVerticalDragGestures`(5)
- [ ] `StyleScope.disabled`(1) *(experimental)*
- [ ] `AwaitPointerEventScope.drag`(3)
Expand Down Expand Up @@ -443,7 +443,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [ ] `LazyListScope.itemsIndexed`(5)
- [ ] `LazyGridScope.itemsIndexed`(6)
- [ ] `LazyStaggeredGridScope.itemsIndexed`(6)
- [ ] `KeyboardActions`(1)
- [x] `KeyboardActions`(1) → covered by `OutlinedTextField.KeyboardActions`
- [ ] `LazyGridPrefetchStrategy`(1) *(experimental)*
- [ ] `LazyLayoutCacheWindow`(3) *(experimental)*
- [ ] `LazyLayoutKeyIndexMap`(3)
Expand Down Expand Up @@ -1427,7 +1427,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).

## `ui`

### @Composable functions — 10/26 (38%)
### @Composable functions — 11/26 (42%)

- [x] `AndroidView`(6) → type match
- [x] `booleanResource`(1) → covered by `ComposeExtensions.BooleanResource`
Expand All @@ -1439,7 +1439,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [x] `integerArrayResource`(1) → covered by `ComposeExtensions.IntegerArrayResource`
- [x] `integerResource`(1) → covered by `ComposeExtensions.IntegerResource`
- [ ] `InterceptPlatformTextInput`(3) *(experimental)*
- [ ] `Layout`(4)
- [x] `Layout`(4) → type match
- [ ] `LookaheadScope`(1)
- [ ] `mediaQuery`(1) *(experimental)*
- [x] `painterResource`(1) → covered by `ComposeExtensions.PainterResource`
Expand All @@ -1456,7 +1456,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [ ] `SubcomposeLayout`(4)
- [ ] `TestModifierUpdaterLayout`(1)

### Other top-level functions — 20/224 (9%)
### Other top-level functions — 22/224 (10%)

- [ ] `addPathNodes`(1)
- [ ] `VelocityTracker.addPointerInputChange`(2)
Expand Down Expand Up @@ -1538,7 +1538,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [x] `Modifier.graphicsLayer`(20) → Modifier extension
- [ ] `GraphicsLayerScope`(0)
- [ ] `ImageVector.Builder.group`(11)
- [ ] `SemanticsPropertyReceiver.heading`(0)
- [x] `SemanticsPropertyReceiver.heading`(0) → SemanticsPropertyReceiver.* extension
- [ ] `SemanticsPropertyReceiver.hideFromAccessibility`(0)
- [ ] `ImageBitmap.Companion.imageResource`(2)
- [ ] `SemanticsPropertyReceiver.indexForKey`(1)
Expand All @@ -1565,7 +1565,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [ ] `PointerButtons.isPressed`(1)
- [ ] `Modifier.keepScreenOn`(0)
- [ ] `Key`(1)
- [ ] `Modifier.layout`(1)
- [x] `Modifier.layout`(1) → Modifier extension
- [ ] `Modifier.layoutBounds`(1)
- [ ] `lerp`(3)
- [ ] `LookaheadScope.lookaheadScopeCoordinates`(1)
Expand Down Expand Up @@ -1767,7 +1767,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [ ] `VerticalRuler`
- [ ] `VNode`

### Interfaces — 5/125 (4%)
### Interfaces — 8/125 (6%)

- [ ] `AccessibilityManager`
- [x] `Alignment` → type match
Expand Down Expand Up @@ -1831,12 +1831,12 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [ ] `LayoutModifier`
- [ ] `LayoutModifierNode`
- [ ] `LookaheadScope`
- [ ] `Measurable`
- [x] `Measurable` → type match
- [ ] `Measured`
- [ ] `MeasuredSizeAwareModifierNode`
- [ ] `MeasurePolicy`
- [ ] `MeasureResult`
- [ ] `MeasureScope`
- [x] `MeasureResult` → type match
- [x] `MeasureScope` → type match
- [x] `Modifier` → type match
- [ ] `ModifierLocalConsumer`
- [ ] `ModifierLocalModifierNode`
Expand Down Expand Up @@ -1938,7 +1938,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [ ] `TouchBoundsExpansion`
- [x] `TransformOrigin` → type match

### Top-level properties — 6/106 (6%)
### Top-level properties — 7/106 (7%)

- [ ] `SemanticsPropertyReceiver.accessibilityClassName`
- [ ] `SemanticsPropertyReceiver.collectionInfo by SemanticsProperties.CollectionInfo`
Expand Down Expand Up @@ -1977,7 +1977,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [ ] `FirstBaseline`
- [ ] `SemanticsPropertyReceiver.focused by SemanticsProperties.Focused`
- [ ] `SemanticsPropertyReceiver.horizontalScrollAxisRange`
- [ ] `SemanticsPropertyReceiver.imeAction by SemanticsProperties.ImeAction`
- [x] `SemanticsPropertyReceiver.imeAction by SemanticsProperties.ImeAction` → type match
- [ ] `SemanticsPropertyReceiver.inputText by SemanticsProperties.InputText`
- [ ] `SemanticsPropertyReceiver.inputTextSuggestionState`
- [ ] `SemanticsPropertyReceiver.isContainer by SemanticsProperties.IsContainer`
Expand Down Expand Up @@ -2383,7 +2383,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).

- [ ] `ResolvedTextDirection`

### Value classes — 4/17 (24%)
### Value classes — 5/17 (29%)

- [ ] `BaselineShift`
- [ ] `DeviceFontFamilyName`
Expand All @@ -2392,7 +2392,7 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).
- [x] `FontStyle` → type match
- [ ] `FontSynthesis`
- [ ] `Hyphens`
- [ ] `ImeAction`
- [x] `ImeAction` → type match
- [ ] `KeyboardCapitalization`
- [x] `KeyboardType` → type match
- [ ] `LineBreak`
Expand All @@ -2405,16 +2405,16 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).

## `ui-unit`

### Other top-level functions — 1/22 (5%)
### Other top-level functions — 2/22 (9%)

- [ ] `Dp.coerceAtLeast`(1) → receiver Dp present, ext missing
- [ ] `Dp.coerceAtMost`(1) → receiver Dp present, ext missing
- [ ] `Dp.coerceIn`(2) → receiver Dp present, ext missing
- [ ] `Constraints.constrain`(1)
- [ ] `Constraints`(5)
- [ ] `Constraints.constrain`(1) → receiver Constraints present, ext missing
- [x] `Constraints`(5) → type match
- [ ] `Density`(2)
- [ ] `IntRect`(2)
- [ ] `Constraints.isSatisfiedBy`(1)
- [ ] `Constraints.isSatisfiedBy`(1) → receiver Constraints present, ext missing
- [ ] `lerp`(3)
- [x] `Constraints.offset`(2) → Constraints.* extension
- [ ] `Rect.roundToIntRect`(0)
Expand Down Expand Up @@ -2452,9 +2452,9 @@ bumping a Compose package version. Coverage is symbol-level (short-name match).

- [ ] `LayoutDirection`

### Value classes — 1/9 (11%)
### Value classes — 2/9 (22%)

- [ ] `Constraints`
- [x] `Constraints` → type match
- [x] `Dp` → type match
- [ ] `DpOffset`
- [ ] `DpSize`
Expand Down Expand Up @@ -2550,6 +2550,7 @@ a Compose API the matcher didn't pair up.
- `AndroidX.Compose.CompositionLocal<T>`
- `AndroidX.Compose.CustomTab`
- `AndroidX.Compose.DateRangePickerDialog`
- `AndroidX.Compose.DateRangeSelectableDates`
- `AndroidX.Compose.Icons.AutoMirrored.Default`
- `AndroidX.Compose.Icons.Default`
- `AndroidX.Compose.DerivedState<T>`
Expand All @@ -2562,6 +2563,7 @@ a Compose API the matcher didn't pair up.
- `AndroidX.Compose.Icons`
- `AndroidX.Compose.IState<T>`
- `AndroidX.Compose.IStateFlow<T>`
- `AndroidX.Compose.KeyboardActionsHelper`
- `AndroidX.Compose.KeyboardOptionsCompanion`
- `AndroidX.Compose.LargeFlexibleTopAppBar`
- `AndroidX.Compose.LocalColorScheme`
Expand All @@ -2577,6 +2579,7 @@ a Compose API the matcher didn't pair up.
- `AndroidX.Compose.NavOptions`
- `AndroidX.Compose.Icons.AutoMirrored.Outlined`
- `AndroidX.Compose.Icons.Outlined`
- `AndroidX.Compose.PlacementScope`
- `AndroidX.Compose.Resource`
- `AndroidX.Compose.Icons.AutoMirrored.Rounded`
- `AndroidX.Compose.Icons.Rounded`
Expand Down
95 changes: 81 additions & 14 deletions samples/JetNews/InterestsScreen.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Placeable = AndroidX.Compose.UI.Layout.Placeable;

namespace AndroidX.Compose.Samples.JetNews;

/// <summary>
/// JetNews interests screen — a <see cref="PrimaryTabRow"/> with three
/// tabs (Topics / People / Publications) and a per-tab toggleable
/// list. The upstream sample renders Topics in an adaptive two-column
/// custom <c>Layout</c> on wide screens; we render a single column.
/// list. Topics are rendered through a custom <see cref="Layout"/>
/// that re-balances rows across two columns on wide screens.
/// </summary>
public static class InterestsScreen
{
Expand Down Expand Up @@ -48,7 +50,7 @@ static Column BuildBody(
},
selectedTab.Value switch
{
0 => (ComposableNode) BuildTopics(selectedTopics),
0 => BuildTopics(selectedTopics),
1 => BuildSimpleList(InterestsRepo.People, selectedPeople),
_ => BuildSimpleList(InterestsRepo.Publications, selectedPublications),
},
Expand All @@ -62,23 +64,88 @@ static Tab BuildTab(MutableState<int> selectedTab, int index, string label) =>
Text = new Text(label) { FontSize = 14 },
};

static Column BuildTopics(MutableStateList<string> selected)
static ComposableNode BuildTopics(MutableStateList<string> selected) =>
new Composed(c =>
{
// Topics content can exceed viewport height (especially landscape),
// so the outer column needs vertical scrolling.
var scroll = c.Remember(() => new ScrollState());
var col = new Column
{
Modifier.FillMaxWidth().VerticalScroll(scroll),
};
foreach (var section in InterestsRepo.Topics)
{
col.Add(BuildSectionHeader(section.Key));
var rows = new List<ComposableNode>();
foreach (var topic in section.Value)
{
var key = $"{section.Key}/{topic}";
rows.Add(BuildToggleRow(topic, selected.Contains(key), () => Toggle(selected, key)));
}
col.Add(BuildAdaptiveTopicSection(rows));
col.Add(new HorizontalDivider
{
Modifier = Modifier.Padding(horizontal: 16, vertical: 8),
});
}
return col;
});

// Mirrors upstream JetNews's `InterestsAdaptiveContentLayout` — bins
// topic rows into N columns whose count is driven by the parent's
// available width. Backed by Compose's low-level `Layout` primitive.
static Layout BuildAdaptiveTopicSection(IReadOnlyList<ComposableNode> rows)
{
var col = new Column { Modifier.FillMaxWidth() };
foreach (var section in InterestsRepo.Topics)
const int multiColumnBreakpointPx = 600;

var layout = new Layout(measurePolicy: (scope, measurables, constraints) =>
{
col.Add(BuildSectionHeader(section.Key));
foreach (var topic in section.Value)
int max = constraints.HasBoundedWidth
? constraints.MaxWidth
: multiColumnBreakpointPx;
int columns = max >= multiColumnBreakpointPx ? 2 : 1;
int columnWidth = max / columns;

var childConstraints = Constraints.FixedWidth(columnWidth);
var placeables = new Placeable[measurables.Count];
var columnHeights = new int[columns];
var columnAssign = new int[measurables.Count];
for (int i = 0; i < measurables.Count; i++)
{
var key = $"{section.Key}/{topic}";
col.Add(BuildToggleRow(topic, selected.Contains(key), () => Toggle(selected, key)));
placeables[i] = measurables[i].Measure(childConstraints);
int h = placeables[i].Height;
int target = 0;
for (int c = 1; c < columns; c++)
if (columnHeights[c] < columnHeights[target]) target = c;
columnAssign[i] = target;
columnHeights[target] += h;
}
col.Add(new HorizontalDivider

int totalHeight = 0;
for (int c = 0; c < columns; c++)
if (columnHeights[c] > totalHeight) totalHeight = columnHeights[c];

return scope.Layout(columnWidth * columns, totalHeight, placement =>
{
Modifier = Modifier.Padding(horizontal: 16, vertical: 8),
var yByColumn = new int[columns];
for (int i = 0; i < placeables.Length; i++)
{
int col = columnAssign[i];
placement.PlaceRelative(
placeables[i],
x: col * columnWidth,
y: yByColumn[col]);
yByColumn[col] += placeables[i].Height;
}
});
}
return col;
})
{
Modifier.FillMaxWidth(),
};
foreach (var row in rows)
layout.Add(row);
return layout;
}

static LazyColumn<string> BuildSimpleList(IReadOnlyList<string> items,
Expand Down
Loading
Loading