From 31bf9dff3701b46e6a5ed89669dfce82d994c23b Mon Sep 17 00:00:00 2001 From: "Oleksandr.Karpovich" Date: Tue, 6 Jan 2026 12:20:54 +0100 Subject: [PATCH 01/19] Implement toRadians function in commonMain and remove expect/actual Test: existing tests Change-Id: Id53ff79d58d283d9c5ead18debdec73dc4b460c2 --- .../kotlin/androidx/compose/animation/core/ArcSpline.kt | 6 +++++- .../compose/animation/core/ArcSpline.jvmAndAndroid.kt | 5 ----- .../compose/animation/core/ArcSpline.linuxx64Stubs.kt | 3 --- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt index 5f77d0171189d..9cc7b15e15325 100644 --- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt +++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/ArcSpline.kt @@ -387,6 +387,10 @@ private const val HalfPi = (PI * 0.5).toFloat() private val OurPercentCache: FloatArray = FloatArray(91) -internal expect inline fun toRadians(value: Double): Double +@Suppress("NOTHING_TO_INLINE") +internal inline fun toRadians(value: Double): Double { + // No Kotlin multiplatform function out of a box, but it's a trivial calculation + return value * (PI / 180.0) +} internal expect inline fun binarySearch(array: FloatArray, position: Float): Int diff --git a/compose/animation/animation-core/src/jvmAndAndroidMain/kotlin/androidx/compose/animation/core/ArcSpline.jvmAndAndroid.kt b/compose/animation/animation-core/src/jvmAndAndroidMain/kotlin/androidx/compose/animation/core/ArcSpline.jvmAndAndroid.kt index 5ade8f9e4eac4..728488d72e810 100644 --- a/compose/animation/animation-core/src/jvmAndAndroidMain/kotlin/androidx/compose/animation/core/ArcSpline.jvmAndAndroid.kt +++ b/compose/animation/animation-core/src/jvmAndAndroidMain/kotlin/androidx/compose/animation/core/ArcSpline.jvmAndAndroid.kt @@ -16,11 +16,6 @@ package androidx.compose.animation.core -@Suppress("NOTHING_TO_INLINE") -internal actual inline fun toRadians(value: Double): Double { - return Math.toRadians(value) -} - @Suppress("NOTHING_TO_INLINE") internal actual inline fun binarySearch(array: FloatArray, position: Float): Int { return array.binarySearch(position) diff --git a/compose/animation/animation-core/src/linuxx64StubsMain/kotlin/androidx/compose/animation/core/ArcSpline.linuxx64Stubs.kt b/compose/animation/animation-core/src/linuxx64StubsMain/kotlin/androidx/compose/animation/core/ArcSpline.linuxx64Stubs.kt index d8d41dd9de498..9bd4c8e27e781 100644 --- a/compose/animation/animation-core/src/linuxx64StubsMain/kotlin/androidx/compose/animation/core/ArcSpline.linuxx64Stubs.kt +++ b/compose/animation/animation-core/src/linuxx64StubsMain/kotlin/androidx/compose/animation/core/ArcSpline.linuxx64Stubs.kt @@ -16,9 +16,6 @@ package androidx.compose.animation.core -@Suppress("NOTHING_TO_INLINE") -internal actual inline fun toRadians(value: Double): Double = implementedInJetBrainsFork() - @Suppress("NOTHING_TO_INLINE") internal actual inline fun binarySearch(array: FloatArray, position: Float): Int = implementedInJetBrainsFork() From 88bdb75e1d3bad68f2c0a39a377f6aada2462916 Mon Sep 17 00:00:00 2001 From: Yashwanth Gajji Date: Tue, 13 Jan 2026 15:53:15 -0800 Subject: [PATCH 02/19] Simplifying SubspaceLayout content lambda function Bug: 475610120 Test: N/A Change-Id: I00a3ab3fe21b194fcd74c54db157bb567e273fc4 --- .../androidx/xr/compose/subspace/layout/SubspaceLayout.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/layout/SubspaceLayout.kt b/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/layout/SubspaceLayout.kt index ec0e8f49cb413..f6c8fdbbacb4a 100644 --- a/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/layout/SubspaceLayout.kt +++ b/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/layout/SubspaceLayout.kt @@ -127,7 +127,7 @@ public inline fun SubspaceLayout( set(coreEntity, SetCoreEntity) set(modifier, SetModifier) }, - content = { content() }, + content = content, ) } } @@ -289,7 +289,7 @@ internal inline fun SubspaceLayout( set(coreGroupEntity, SetCoreEntity) set(modifier, SetModifier) }, - content = { content() }, + content = content, ) } } From cda6fbcb2dc78f9b30e6e955c7cb5d4c2063f386 Mon Sep 17 00:00:00 2001 From: Prashant Date: Tue, 9 Dec 2025 03:44:26 +0000 Subject: [PATCH 03/19] Introduce Grid layout API surface Introduces the public API surface for the new `Grid` layout composable in `androidx.compose.foundation.layout`. This layout enables developers to arrange UI elements in a two-dimensional grid structure. Key components introduced: * `Grid`: The main composable for creating the layout, taking a configuration lambda to define the structure. * `GridConfigurationScope`: The DSL receiver for defining track sizes (rows/columns) and gaps. * `GridTrackSize`: Defines sizing specifications, including `Fixed`, `Percentage`, `Flex` (using the new `Fr` unit), `MinContent`, `MaxContent`, and `Auto`. * `GridFlow`: Controls the direction of auto-placement (Row or Column). * `Modifier.gridItem`: Allows explicit placement and spanning of items within grid cells using 1-based indexing. Convenience extensions `columns(...)` and `rows(...)` are provided on `GridConfigurationScope` for defining multiple tracks. Note that the implementation of the `Grid` layout logic is currently stubbed. This commit strictly establishes the API contracts, with the measure policy logic to follow in subsequent changes. Relnote: "Introduced the API for `Grid`, a new non-lazy 2D layout composable inspired by CSS Grid. This allows defining explicit grid structures with `Fixed`, `Percentage`, `Flex(Fr)`, and content-based track sizes via the `config` block. Items are placed using `Modifier.gridItem()`." Bug: 462550392 Test: NA Change-Id: I04907884457dbffb9653b09807c03b96506517c0 --- .../foundation-layout/api/current.txt | 104 ++++ .../api/restricted_current.txt | 111 ++++ .../foundation-layout/bcv/native/current.txt | 93 +++ .../compose/foundation/layout/Grid.kt | 554 ++++++++++++++++++ 4 files changed, 862 insertions(+) create mode 100644 compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt diff --git a/compose/foundation/foundation-layout/api/current.txt b/compose/foundation/foundation-layout/api/current.txt index ecfcd2e395dd3..5382a9981ee2a 100644 --- a/compose/foundation/foundation-layout/api/current.txt +++ b/compose/foundation/foundation-layout/api/current.txt @@ -275,6 +275,110 @@ package androidx.compose.foundation.layout { method @BytecodeOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public static androidx.compose.ui.Modifier! fillMaxRowHeight$default(androidx.compose.foundation.layout.FlowRowScope!, androidx.compose.ui.Modifier!, float, int, Object!); } + @kotlin.jvm.JvmInline public final value class Fr { + ctor @KotlinOnly public Fr(float value); + method @BytecodeOnly public static androidx.compose.foundation.layout.Fr! box-impl(float); + method @BytecodeOnly public static float constructor-impl(float); + method @InaccessibleFromKotlin public float getValue(); + method @BytecodeOnly public float unbox-impl(); + property public float value; + } + + @androidx.compose.foundation.layout.LayoutScopeMarker public interface GridConfigurationScope extends androidx.compose.ui.unit.Density { + method @KotlinOnly public void column(androidx.compose.foundation.layout.Fr weight); + method @KotlinOnly public void column(androidx.compose.foundation.layout.GridTrackSize size); + method @KotlinOnly public void column(androidx.compose.ui.unit.Dp size); + method public void column(float percentage); + method @BytecodeOnly public void column-0680j_4(float); + method @BytecodeOnly public void column-118E5d0(long); + method @BytecodeOnly public void column-XZblgos(float); + method @KotlinOnly public void columnGap(androidx.compose.ui.unit.Dp gap); + method @BytecodeOnly public void columnGap-0680j_4(float); + method @KotlinOnly public void gap(androidx.compose.ui.unit.Dp all); + method @KotlinOnly public void gap(androidx.compose.ui.unit.Dp row, androidx.compose.ui.unit.Dp column); + method @BytecodeOnly public void gap-0680j_4(float); + method @BytecodeOnly public void gap-YgX7TsA(float, float); + method @BytecodeOnly public int getFlow-ITJdzs4(); + method @BytecodeOnly public default float getFr-9P9H2UQ(double); + method @BytecodeOnly public default float getFr-9P9H2UQ(float); + method @BytecodeOnly public default float getFr-9P9H2UQ(int); + method @KotlinOnly public void row(androidx.compose.foundation.layout.Fr weight); + method @KotlinOnly public void row(androidx.compose.foundation.layout.GridTrackSize size); + method @KotlinOnly public void row(androidx.compose.ui.unit.Dp size); + method public void row(float percentage); + method @BytecodeOnly public void row-0680j_4(float); + method @BytecodeOnly public void row-118E5d0(long); + method @BytecodeOnly public void row-XZblgos(float); + method @KotlinOnly public void rowGap(androidx.compose.ui.unit.Dp gap); + method @BytecodeOnly public void rowGap-0680j_4(float); + method @BytecodeOnly public void setFlow-4t4_IgM(int); + property public abstract androidx.compose.foundation.layout.GridFlow flow; + property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr int.fr; + property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr float.fr; + property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr double.fr; + } + + @kotlin.jvm.JvmInline public final value class GridFlow { + method @BytecodeOnly public static androidx.compose.foundation.layout.GridFlow! box-impl(int); + method @BytecodeOnly public int unbox-impl(); + field public static final androidx.compose.foundation.layout.GridFlow.Companion Companion; + } + + public static final class GridFlow.Companion { + method @BytecodeOnly public int getColumn-ITJdzs4(); + method @BytecodeOnly public int getRow-ITJdzs4(); + property public inline androidx.compose.foundation.layout.GridFlow Column; + property public inline androidx.compose.foundation.layout.GridFlow Row; + } + + public final class GridKt { + method @BytecodeOnly @androidx.compose.runtime.Composable public static void Grid(kotlin.jvm.functions.Function1, androidx.compose.ui.Modifier?, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); + method @KotlinOnly @androidx.compose.runtime.Composable public static inline void Grid(kotlin.jvm.functions.Function1 config, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1 content); + method public static void columns(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); + method public static void rows(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); + } + + @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable @kotlin.jvm.JvmDefaultWithCompatibility public interface GridScope { + method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, optional int row, optional int column, optional int rowSpan, optional int columnSpan, optional androidx.compose.ui.Alignment alignment); + method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, kotlin.ranges.IntRange rows, kotlin.ranges.IntRange columns, optional androidx.compose.ui.Alignment alignment); + method @BytecodeOnly @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier! gridItem$default(androidx.compose.foundation.layout.GridScope!, androidx.compose.ui.Modifier!, int, int, int, int, androidx.compose.ui.Alignment!, int, Object!); + method @BytecodeOnly @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier! gridItem$default(androidx.compose.foundation.layout.GridScope!, androidx.compose.ui.Modifier!, kotlin.ranges.IntRange!, kotlin.ranges.IntRange!, androidx.compose.ui.Alignment!, int, Object!); + field public static final androidx.compose.foundation.layout.GridScope.Companion Companion; + field public static final int GridIndexUnspecified = 0; // 0x0 + field public static final int MaxGridIndex = 1000; // 0x3e8 + } + + public static final class GridScope.Companion { + property public static int GridIndexUnspecified; + property public static int MaxGridIndex; + field public static final int GridIndexUnspecified = 0; // 0x0 + field public static final int MaxGridIndex = 1000; // 0x3e8 + } + + @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class GridTrackSize implements androidx.compose.foundation.layout.GridTrackSpec { + method @BytecodeOnly public static androidx.compose.foundation.layout.GridTrackSize! box-impl(long); + method @BytecodeOnly public long unbox-impl(); + field public static final androidx.compose.foundation.layout.GridTrackSize.Companion Companion; + } + + public static final class GridTrackSize.Companion { + method @KotlinOnly @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Fixed(androidx.compose.ui.unit.Dp size); + method @BytecodeOnly @androidx.compose.runtime.Stable public long Fixed-psSkOvk(float); + method @KotlinOnly @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Flex(@FloatRange(from=0.0) androidx.compose.foundation.layout.Fr weight); + method @BytecodeOnly @androidx.compose.runtime.Stable public long Flex-KGB9zo8(@FloatRange(from=0.0) float); + method @KotlinOnly @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Percentage(@FloatRange(from=0.0) float value); + method @BytecodeOnly @androidx.compose.runtime.Stable public long Percentage-9Tp3RV8(@FloatRange(from=0.0) float); + method @BytecodeOnly public long getAuto-eyNpfc4(); + method @BytecodeOnly public long getMaxContent-eyNpfc4(); + method @BytecodeOnly public long getMinContent-eyNpfc4(); + property @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Auto; + property @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize MaxContent; + property @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize MinContent; + } + + public sealed exhaustive interface GridTrackSpec { + } + public final class IntrinsicKt { method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier height(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.IntrinsicSize intrinsicSize); method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier requiredHeight(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.IntrinsicSize intrinsicSize); diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt index e9d0231946d28..64d496f56abee 100644 --- a/compose/foundation/foundation-layout/api/restricted_current.txt +++ b/compose/foundation/foundation-layout/api/restricted_current.txt @@ -298,6 +298,117 @@ package androidx.compose.foundation.layout { method @BytecodeOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public static androidx.compose.ui.Modifier! fillMaxRowHeight$default(androidx.compose.foundation.layout.FlowRowScope!, androidx.compose.ui.Modifier!, float, int, Object!); } + @kotlin.jvm.JvmInline public final value class Fr { + ctor @KotlinOnly public Fr(float value); + method @BytecodeOnly public static androidx.compose.foundation.layout.Fr! box-impl(float); + method @BytecodeOnly public static float constructor-impl(float); + method @InaccessibleFromKotlin public float getValue(); + method @BytecodeOnly public float unbox-impl(); + property public float value; + } + + @androidx.compose.foundation.layout.LayoutScopeMarker public interface GridConfigurationScope extends androidx.compose.ui.unit.Density { + method @KotlinOnly public void column(androidx.compose.foundation.layout.Fr weight); + method @KotlinOnly public void column(androidx.compose.foundation.layout.GridTrackSize size); + method @KotlinOnly public void column(androidx.compose.ui.unit.Dp size); + method public void column(float percentage); + method @BytecodeOnly public void column-0680j_4(float); + method @BytecodeOnly public void column-118E5d0(long); + method @BytecodeOnly public void column-XZblgos(float); + method @KotlinOnly public void columnGap(androidx.compose.ui.unit.Dp gap); + method @BytecodeOnly public void columnGap-0680j_4(float); + method @KotlinOnly public void gap(androidx.compose.ui.unit.Dp all); + method @KotlinOnly public void gap(androidx.compose.ui.unit.Dp row, androidx.compose.ui.unit.Dp column); + method @BytecodeOnly public void gap-0680j_4(float); + method @BytecodeOnly public void gap-YgX7TsA(float, float); + method @BytecodeOnly public int getFlow-ITJdzs4(); + method @BytecodeOnly public default float getFr-9P9H2UQ(double); + method @BytecodeOnly public default float getFr-9P9H2UQ(float); + method @BytecodeOnly public default float getFr-9P9H2UQ(int); + method @KotlinOnly public void row(androidx.compose.foundation.layout.Fr weight); + method @KotlinOnly public void row(androidx.compose.foundation.layout.GridTrackSize size); + method @KotlinOnly public void row(androidx.compose.ui.unit.Dp size); + method public void row(float percentage); + method @BytecodeOnly public void row-0680j_4(float); + method @BytecodeOnly public void row-118E5d0(long); + method @BytecodeOnly public void row-XZblgos(float); + method @KotlinOnly public void rowGap(androidx.compose.ui.unit.Dp gap); + method @BytecodeOnly public void rowGap-0680j_4(float); + method @BytecodeOnly public void setFlow-4t4_IgM(int); + property public abstract androidx.compose.foundation.layout.GridFlow flow; + property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr int.fr; + property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr float.fr; + property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr double.fr; + } + + @kotlin.jvm.JvmInline public final value class GridFlow { + ctor @KotlinOnly @kotlin.PublishedApi internal GridFlow(int bits); + method @BytecodeOnly public static androidx.compose.foundation.layout.GridFlow! box-impl(int); + method @BytecodeOnly @kotlin.PublishedApi internal static int constructor-impl(int); + method @BytecodeOnly public int unbox-impl(); + field public static final androidx.compose.foundation.layout.GridFlow.Companion Companion; + } + + public static final class GridFlow.Companion { + method @BytecodeOnly public int getColumn-ITJdzs4(); + method @BytecodeOnly public int getRow-ITJdzs4(); + property public inline androidx.compose.foundation.layout.GridFlow Column; + property public inline androidx.compose.foundation.layout.GridFlow Row; + } + + public final class GridKt { + method @BytecodeOnly @androidx.compose.runtime.Composable public static void Grid(kotlin.jvm.functions.Function1, androidx.compose.ui.Modifier?, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); + method @KotlinOnly @androidx.compose.runtime.Composable public static inline void Grid(kotlin.jvm.functions.Function1 config, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1 content); + method public static void columns(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); + method public static void rows(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); + } + + @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable @kotlin.jvm.JvmDefaultWithCompatibility public interface GridScope { + method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, optional int row, optional int column, optional int rowSpan, optional int columnSpan, optional androidx.compose.ui.Alignment alignment); + method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, kotlin.ranges.IntRange rows, kotlin.ranges.IntRange columns, optional androidx.compose.ui.Alignment alignment); + method @BytecodeOnly @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier! gridItem$default(androidx.compose.foundation.layout.GridScope!, androidx.compose.ui.Modifier!, int, int, int, int, androidx.compose.ui.Alignment!, int, Object!); + method @BytecodeOnly @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier! gridItem$default(androidx.compose.foundation.layout.GridScope!, androidx.compose.ui.Modifier!, kotlin.ranges.IntRange!, kotlin.ranges.IntRange!, androidx.compose.ui.Alignment!, int, Object!); + field public static final androidx.compose.foundation.layout.GridScope.Companion Companion; + field public static final int GridIndexUnspecified = 0; // 0x0 + field public static final int MaxGridIndex = 1000; // 0x3e8 + } + + public static final class GridScope.Companion { + property public static int GridIndexUnspecified; + property public static int MaxGridIndex; + field public static final int GridIndexUnspecified = 0; // 0x0 + field public static final int MaxGridIndex = 1000; // 0x3e8 + } + + @kotlin.PublishedApi internal final class GridScopeInstance implements androidx.compose.foundation.layout.GridScope { + method public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, int row, int column, int rowSpan, int columnSpan, androidx.compose.ui.Alignment alignment); + method public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, kotlin.ranges.IntRange rows, kotlin.ranges.IntRange columns, androidx.compose.ui.Alignment alignment); + } + + @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class GridTrackSize implements androidx.compose.foundation.layout.GridTrackSpec { + method @BytecodeOnly public static androidx.compose.foundation.layout.GridTrackSize! box-impl(long); + method @BytecodeOnly public long unbox-impl(); + field public static final androidx.compose.foundation.layout.GridTrackSize.Companion Companion; + } + + public static final class GridTrackSize.Companion { + method @KotlinOnly @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Fixed(androidx.compose.ui.unit.Dp size); + method @BytecodeOnly @androidx.compose.runtime.Stable public long Fixed-psSkOvk(float); + method @KotlinOnly @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Flex(@FloatRange(from=0.0) androidx.compose.foundation.layout.Fr weight); + method @BytecodeOnly @androidx.compose.runtime.Stable public long Flex-KGB9zo8(@FloatRange(from=0.0) float); + method @KotlinOnly @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Percentage(@FloatRange(from=0.0) float value); + method @BytecodeOnly @androidx.compose.runtime.Stable public long Percentage-9Tp3RV8(@FloatRange(from=0.0) float); + method @BytecodeOnly public long getAuto-eyNpfc4(); + method @BytecodeOnly public long getMaxContent-eyNpfc4(); + method @BytecodeOnly public long getMinContent-eyNpfc4(); + property @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Auto; + property @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize MaxContent; + property @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize MinContent; + } + + public sealed exhaustive interface GridTrackSpec { + } + public final class IntrinsicKt { method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier height(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.IntrinsicSize intrinsicSize); method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier requiredHeight(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.IntrinsicSize intrinsicSize); diff --git a/compose/foundation/foundation-layout/bcv/native/current.txt b/compose/foundation/foundation-layout/bcv/native/current.txt index c4aa43818895d..f6e98943a2843 100644 --- a/compose/foundation/foundation-layout/bcv/native/current.txt +++ b/compose/foundation/foundation-layout/bcv/native/current.txt @@ -54,6 +54,44 @@ abstract interface androidx.compose.foundation.layout/FlowColumnScope : androidx abstract interface androidx.compose.foundation.layout/FlowRowScope : androidx.compose.foundation.layout/RowScope // androidx.compose.foundation.layout/FlowRowScope|null[0] +abstract interface androidx.compose.foundation.layout/GridConfigurationScope : androidx.compose.ui.unit/Density { // androidx.compose.foundation.layout/GridConfigurationScope|null[0] + open val fr // androidx.compose.foundation.layout/GridConfigurationScope.fr|@kotlin.Double{}fr[0] + open fun (kotlin/Double).(): androidx.compose.foundation.layout/Fr // androidx.compose.foundation.layout/GridConfigurationScope.fr.|@kotlin.Double(){}[0] + open val fr // androidx.compose.foundation.layout/GridConfigurationScope.fr|@kotlin.Float{}fr[0] + open fun (kotlin/Float).(): androidx.compose.foundation.layout/Fr // androidx.compose.foundation.layout/GridConfigurationScope.fr.|@kotlin.Float(){}[0] + open val fr // androidx.compose.foundation.layout/GridConfigurationScope.fr|@kotlin.Int{}fr[0] + open fun (kotlin/Int).(): androidx.compose.foundation.layout/Fr // androidx.compose.foundation.layout/GridConfigurationScope.fr.|@kotlin.Int(){}[0] + + abstract var flow // androidx.compose.foundation.layout/GridConfigurationScope.flow|{}flow[0] + abstract fun (): androidx.compose.foundation.layout/GridFlow // androidx.compose.foundation.layout/GridConfigurationScope.flow.|(){}[0] + abstract fun (androidx.compose.foundation.layout/GridFlow) // androidx.compose.foundation.layout/GridConfigurationScope.flow.|(androidx.compose.foundation.layout.GridFlow){}[0] + + abstract fun column(androidx.compose.foundation.layout/Fr) // androidx.compose.foundation.layout/GridConfigurationScope.column|column(androidx.compose.foundation.layout.Fr){}[0] + abstract fun column(androidx.compose.foundation.layout/GridTrackSize) // androidx.compose.foundation.layout/GridConfigurationScope.column|column(androidx.compose.foundation.layout.GridTrackSize){}[0] + abstract fun column(androidx.compose.ui.unit/Dp) // androidx.compose.foundation.layout/GridConfigurationScope.column|column(androidx.compose.ui.unit.Dp){}[0] + abstract fun column(kotlin/Float) // androidx.compose.foundation.layout/GridConfigurationScope.column|column(kotlin.Float){}[0] + abstract fun columnGap(androidx.compose.ui.unit/Dp) // androidx.compose.foundation.layout/GridConfigurationScope.columnGap|columnGap(androidx.compose.ui.unit.Dp){}[0] + abstract fun gap(androidx.compose.ui.unit/Dp) // androidx.compose.foundation.layout/GridConfigurationScope.gap|gap(androidx.compose.ui.unit.Dp){}[0] + abstract fun gap(androidx.compose.ui.unit/Dp, androidx.compose.ui.unit/Dp) // androidx.compose.foundation.layout/GridConfigurationScope.gap|gap(androidx.compose.ui.unit.Dp;androidx.compose.ui.unit.Dp){}[0] + abstract fun row(androidx.compose.foundation.layout/Fr) // androidx.compose.foundation.layout/GridConfigurationScope.row|row(androidx.compose.foundation.layout.Fr){}[0] + abstract fun row(androidx.compose.foundation.layout/GridTrackSize) // androidx.compose.foundation.layout/GridConfigurationScope.row|row(androidx.compose.foundation.layout.GridTrackSize){}[0] + abstract fun row(androidx.compose.ui.unit/Dp) // androidx.compose.foundation.layout/GridConfigurationScope.row|row(androidx.compose.ui.unit.Dp){}[0] + abstract fun row(kotlin/Float) // androidx.compose.foundation.layout/GridConfigurationScope.row|row(kotlin.Float){}[0] + abstract fun rowGap(androidx.compose.ui.unit/Dp) // androidx.compose.foundation.layout/GridConfigurationScope.rowGap|rowGap(androidx.compose.ui.unit.Dp){}[0] +} + +abstract interface androidx.compose.foundation.layout/GridScope { // androidx.compose.foundation.layout/GridScope|null[0] + abstract fun (androidx.compose.ui/Modifier).gridItem(kotlin.ranges/IntRange, kotlin.ranges/IntRange, androidx.compose.ui/Alignment = ...): androidx.compose.ui/Modifier // androidx.compose.foundation.layout/GridScope.gridItem|gridItem@androidx.compose.ui.Modifier(kotlin.ranges.IntRange;kotlin.ranges.IntRange;androidx.compose.ui.Alignment){}[0] + abstract fun (androidx.compose.ui/Modifier).gridItem(kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/Int = ..., androidx.compose.ui/Alignment = ...): androidx.compose.ui/Modifier // androidx.compose.foundation.layout/GridScope.gridItem|gridItem@androidx.compose.ui.Modifier(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;androidx.compose.ui.Alignment){}[0] + + final object Companion { // androidx.compose.foundation.layout/GridScope.Companion|null[0] + final const val GridIndexUnspecified // androidx.compose.foundation.layout/GridScope.Companion.GridIndexUnspecified|{}GridIndexUnspecified[0] + final fun (): kotlin/Int // androidx.compose.foundation.layout/GridScope.Companion.GridIndexUnspecified.|(){}[0] + final const val MaxGridIndex // androidx.compose.foundation.layout/GridScope.Companion.MaxGridIndex|{}MaxGridIndex[0] + final fun (): kotlin/Int // androidx.compose.foundation.layout/GridScope.Companion.MaxGridIndex.|(){}[0] + } +} + abstract interface androidx.compose.foundation.layout/PaddingValues { // androidx.compose.foundation.layout/PaddingValues|null[0] abstract fun calculateBottomPadding(): androidx.compose.ui.unit/Dp // androidx.compose.foundation.layout/PaddingValues.calculateBottomPadding|calculateBottomPadding(){}[0] abstract fun calculateLeftPadding(androidx.compose.ui.unit/LayoutDirection): androidx.compose.ui.unit/Dp // androidx.compose.foundation.layout/PaddingValues.calculateLeftPadding|calculateLeftPadding(androidx.compose.ui.unit.LayoutDirection){}[0] @@ -95,6 +133,53 @@ abstract interface androidx.compose.foundation.layout/WindowInsets { // androidx final object Companion // androidx.compose.foundation.layout/WindowInsets.Companion|null[0] } +sealed interface androidx.compose.foundation.layout/GridTrackSpec // androidx.compose.foundation.layout/GridTrackSpec|null[0] + +final value class androidx.compose.foundation.layout/Fr { // androidx.compose.foundation.layout/Fr|null[0] + constructor (kotlin/Float) // androidx.compose.foundation.layout/Fr.|(kotlin.Float){}[0] + + final val value // androidx.compose.foundation.layout/Fr.value|{}value[0] + final fun (): kotlin/Float // androidx.compose.foundation.layout/Fr.value.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // androidx.compose.foundation.layout/Fr.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // androidx.compose.foundation.layout/Fr.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // androidx.compose.foundation.layout/Fr.toString|toString(){}[0] +} + +final value class androidx.compose.foundation.layout/GridFlow { // androidx.compose.foundation.layout/GridFlow|null[0] + constructor (kotlin/Int) // androidx.compose.foundation.layout/GridFlow.|(kotlin.Int){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // androidx.compose.foundation.layout/GridFlow.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // androidx.compose.foundation.layout/GridFlow.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // androidx.compose.foundation.layout/GridFlow.toString|toString(){}[0] + + final object Companion { // androidx.compose.foundation.layout/GridFlow.Companion|null[0] + final val Column // androidx.compose.foundation.layout/GridFlow.Companion.Column|{}Column[0] + final inline fun (): androidx.compose.foundation.layout/GridFlow // androidx.compose.foundation.layout/GridFlow.Companion.Column.|(){}[0] + final val Row // androidx.compose.foundation.layout/GridFlow.Companion.Row|{}Row[0] + final inline fun (): androidx.compose.foundation.layout/GridFlow // androidx.compose.foundation.layout/GridFlow.Companion.Row.|(){}[0] + } +} + +final value class androidx.compose.foundation.layout/GridTrackSize : androidx.compose.foundation.layout/GridTrackSpec { // androidx.compose.foundation.layout/GridTrackSize|null[0] + final fun equals(kotlin/Any?): kotlin/Boolean // androidx.compose.foundation.layout/GridTrackSize.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // androidx.compose.foundation.layout/GridTrackSize.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // androidx.compose.foundation.layout/GridTrackSize.toString|toString(){}[0] + + final object Companion { // androidx.compose.foundation.layout/GridTrackSize.Companion|null[0] + final val Auto // androidx.compose.foundation.layout/GridTrackSize.Companion.Auto|{}Auto[0] + final fun (): androidx.compose.foundation.layout/GridTrackSize // androidx.compose.foundation.layout/GridTrackSize.Companion.Auto.|(){}[0] + final val MaxContent // androidx.compose.foundation.layout/GridTrackSize.Companion.MaxContent|{}MaxContent[0] + final fun (): androidx.compose.foundation.layout/GridTrackSize // androidx.compose.foundation.layout/GridTrackSize.Companion.MaxContent.|(){}[0] + final val MinContent // androidx.compose.foundation.layout/GridTrackSize.Companion.MinContent|{}MinContent[0] + final fun (): androidx.compose.foundation.layout/GridTrackSize // androidx.compose.foundation.layout/GridTrackSize.Companion.MinContent.|(){}[0] + + final fun Fixed(androidx.compose.ui.unit/Dp): androidx.compose.foundation.layout/GridTrackSize // androidx.compose.foundation.layout/GridTrackSize.Companion.Fixed|Fixed(androidx.compose.ui.unit.Dp){}[0] + final fun Flex(androidx.compose.foundation.layout/Fr): androidx.compose.foundation.layout/GridTrackSize // androidx.compose.foundation.layout/GridTrackSize.Companion.Flex|Flex(androidx.compose.foundation.layout.Fr){}[0] + final fun Percentage(kotlin/Float): androidx.compose.foundation.layout/GridTrackSize // androidx.compose.foundation.layout/GridTrackSize.Companion.Percentage|Percentage(kotlin.Float){}[0] + } +} + final value class androidx.compose.foundation.layout/WindowInsetsSides { // androidx.compose.foundation.layout/WindowInsetsSides|null[0] final fun equals(kotlin/Any?): kotlin/Boolean // androidx.compose.foundation.layout/WindowInsetsSides.equals|equals(kotlin.Any?){}[0] final fun hashCode(): kotlin/Int // androidx.compose.foundation.layout/WindowInsetsSides.hashCode|hashCode(){}[0] @@ -197,6 +282,11 @@ final object androidx.compose.foundation.layout/ColumnScopeInstance : androidx.c final fun (androidx.compose.ui/Modifier).weight(kotlin/Float, kotlin/Boolean): androidx.compose.ui/Modifier // androidx.compose.foundation.layout/ColumnScopeInstance.weight|weight@androidx.compose.ui.Modifier(kotlin.Float;kotlin.Boolean){}[0] } +final object androidx.compose.foundation.layout/GridScopeInstance : androidx.compose.foundation.layout/GridScope { // androidx.compose.foundation.layout/GridScopeInstance|null[0] + final fun (androidx.compose.ui/Modifier).gridItem(kotlin.ranges/IntRange, kotlin.ranges/IntRange, androidx.compose.ui/Alignment): androidx.compose.ui/Modifier // androidx.compose.foundation.layout/GridScopeInstance.gridItem|gridItem@androidx.compose.ui.Modifier(kotlin.ranges.IntRange;kotlin.ranges.IntRange;androidx.compose.ui.Alignment){}[0] + final fun (androidx.compose.ui/Modifier).gridItem(kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Int, androidx.compose.ui/Alignment): androidx.compose.ui/Modifier // androidx.compose.foundation.layout/GridScopeInstance.gridItem|gridItem@androidx.compose.ui.Modifier(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int;androidx.compose.ui.Alignment){}[0] +} + final object androidx.compose.foundation.layout/RowScopeInstance : androidx.compose.foundation.layout/RowScope { // androidx.compose.foundation.layout/RowScopeInstance|null[0] final fun (androidx.compose.ui/Modifier).align(androidx.compose.ui/Alignment.Vertical): androidx.compose.ui/Modifier // androidx.compose.foundation.layout/RowScopeInstance.align|align@androidx.compose.ui.Modifier(androidx.compose.ui.Alignment.Vertical){}[0] final fun (androidx.compose.ui/Modifier).alignBy(androidx.compose.ui.layout/HorizontalAlignmentLine): androidx.compose.ui/Modifier // androidx.compose.foundation.layout/RowScopeInstance.alignBy|alignBy@androidx.compose.ui.Modifier(androidx.compose.ui.layout.HorizontalAlignmentLine){}[0] @@ -252,6 +342,8 @@ final val androidx.compose.foundation.layout/tappableElement // androidx.compose final val androidx.compose.foundation.layout/waterfall // androidx.compose.foundation.layout/waterfall|@androidx.compose.foundation.layout.WindowInsets.Companion{}waterfall[0] final fun (androidx.compose.foundation.layout/WindowInsets.Companion).(androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.foundation.layout/WindowInsets // androidx.compose.foundation.layout/waterfall.|@androidx.compose.foundation.layout.WindowInsets.Companion(androidx.compose.runtime.Composer?;kotlin.Int){}[0] +final fun (androidx.compose.foundation.layout/GridConfigurationScope).androidx.compose.foundation.layout/columns(kotlin/Array...) // androidx.compose.foundation.layout/columns|columns@androidx.compose.foundation.layout.GridConfigurationScope(kotlin.Array...){}[0] +final fun (androidx.compose.foundation.layout/GridConfigurationScope).androidx.compose.foundation.layout/rows(kotlin/Array...) // androidx.compose.foundation.layout/rows|rows@androidx.compose.foundation.layout.GridConfigurationScope(kotlin.Array...){}[0] final fun (androidx.compose.foundation.layout/PaddingValues).androidx.compose.foundation.layout/calculateEndPadding(androidx.compose.ui.unit/LayoutDirection): androidx.compose.ui.unit/Dp // androidx.compose.foundation.layout/calculateEndPadding|calculateEndPadding@androidx.compose.foundation.layout.PaddingValues(androidx.compose.ui.unit.LayoutDirection){}[0] final fun (androidx.compose.foundation.layout/PaddingValues).androidx.compose.foundation.layout/calculateStartPadding(androidx.compose.ui.unit/LayoutDirection): androidx.compose.ui.unit/Dp // androidx.compose.foundation.layout/calculateStartPadding|calculateStartPadding@androidx.compose.foundation.layout.PaddingValues(androidx.compose.ui.unit.LayoutDirection){}[0] final fun (androidx.compose.foundation.layout/PaddingValues).androidx.compose.foundation.layout/minus(androidx.compose.foundation.layout/PaddingValues): androidx.compose.foundation.layout/PaddingValues // androidx.compose.foundation.layout/minus|minus@androidx.compose.foundation.layout.PaddingValues(androidx.compose.foundation.layout.PaddingValues){}[0] @@ -360,4 +452,5 @@ final fun androidx.compose.foundation.layout/rowMeasurePolicy(androidx.compose.f final fun androidx.compose.foundation.layout/rowMeasurementHelper(androidx.compose.foundation.layout/Arrangement.Horizontal, androidx.compose.foundation.layout/Arrangement.Vertical, kotlin/Int, androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.ui.layout/MeasurePolicy // androidx.compose.foundation.layout/rowMeasurementHelper|rowMeasurementHelper(androidx.compose.foundation.layout.Arrangement.Horizontal;androidx.compose.foundation.layout.Arrangement.Vertical;kotlin.Int;androidx.compose.runtime.Composer?;kotlin.Int){}[0] final inline fun androidx.compose.foundation.layout/Box(androidx.compose.ui/Modifier?, androidx.compose.ui/Alignment?, kotlin/Boolean, kotlin/Function3, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // androidx.compose.foundation.layout/Box|Box(androidx.compose.ui.Modifier?;androidx.compose.ui.Alignment?;kotlin.Boolean;kotlin.Function3;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0] final inline fun androidx.compose.foundation.layout/Column(androidx.compose.ui/Modifier?, androidx.compose.foundation.layout/Arrangement.Vertical?, androidx.compose.ui/Alignment.Horizontal?, kotlin/Function3, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // androidx.compose.foundation.layout/Column|Column(androidx.compose.ui.Modifier?;androidx.compose.foundation.layout.Arrangement.Vertical?;androidx.compose.ui.Alignment.Horizontal?;kotlin.Function3;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0] +final inline fun androidx.compose.foundation.layout/Grid(noinline kotlin/Function1, androidx.compose.ui/Modifier?, kotlin/Function3, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // androidx.compose.foundation.layout/Grid|Grid(kotlin.Function1;androidx.compose.ui.Modifier?;kotlin.Function3;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0] final inline fun androidx.compose.foundation.layout/Row(androidx.compose.ui/Modifier?, androidx.compose.foundation.layout/Arrangement.Horizontal?, androidx.compose.ui/Alignment.Vertical?, kotlin/Function3, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // androidx.compose.foundation.layout/Row|Row(androidx.compose.ui.Modifier?;androidx.compose.foundation.layout.Arrangement.Horizontal?;androidx.compose.ui.Alignment.Vertical?;kotlin.Function3;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0] diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt new file mode 100644 index 0000000000000..daa683574a782 --- /dev/null +++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt @@ -0,0 +1,554 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.foundation.layout + +import androidx.annotation.FloatRange +import androidx.compose.foundation.layout.GridScope.Companion.GridIndexUnspecified +import androidx.compose.foundation.layout.GridScope.Companion.MaxGridIndex +import androidx.compose.foundation.layout.internal.JvmDefaultWithCompatibility +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.MeasurePolicy +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.ParentDataModifierNode +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import kotlin.jvm.JvmInline + +/** + * A 2D layout composable that arranges children into a grid of rows and columns. + * + * The [Grid] allows defining explicit tracks (columns and rows) with various sizing capabilities, + * including fixed sizes (`dp`), flexible fractions (`fr`), percentages, and content-based sizing + * (`Auto`). + * + * **Key Features:** + * * **Explicit vs. Implicit:** You define the main structure via [config] (explicit tracks). If + * items are placed outside these defined bounds, or if auto-placement creates new rows/columns, + * the grid automatically extends using implicit sizing (defaults to `Auto`). + * * **Flexible Sizing:** Use [Fr] units (e.g., `1.fr`, `2.fr`) to distribute available space + * proportionally among tracks. + * * **Auto-placement:** Items without a specific [GridScope.gridItem] modifier flow automatically + * into the next available cell based on the configured [GridFlow]. . + * + * @param config A block that defines the columns, rows, and gaps of the grid. This block runs + * during the measure pass, enabling efficient updates based on state. + * @param modifier The modifier to be applied to the layout. + * @param content The content of the grid. Direct children can use [GridScope.gridItem] to configure + * their position and span. + * @see GridScope.gridItem + * @see GridConfigurationScope + */ +@Composable +inline fun Grid( + noinline config: GridConfigurationScope.() -> Unit, + modifier: Modifier = Modifier, + content: @Composable GridScope.() -> Unit, +) { + Layout( + content = { GridScopeInstance.content() }, + modifier = modifier, + measurePolicy = + MeasurePolicy { _, constraints -> + // Implementation to be added in follow-up CL + layout(constraints.minWidth, constraints.minHeight) {} + }, + ) +} + +/** Scope for the children of [Grid]. */ +@LayoutScopeMarker +@Immutable +@JvmDefaultWithCompatibility +interface GridScope { + /** + * Configures the position, span, and alignment of an element within a [Grid] layout. + * + * Apply this modifier to direct children of a [Grid] composable. + * + * **Default Behavior:** If this modifier is not applied to a child, the child will be + * automatically placed in the next available cell (spanning 1 row and 1 column) according to + * the configured [GridFlow]. + * + * **Indexing:** Grid row and column indices are **1-based**. + * * **Positive** values count from the start (1 is the first row/column). + * * **Negative** values count from the end (-1 is the last explicitly defined row/column). + * + * **Auto-placement:** If [row] or [column] are left to their default value + * ([GridIndexUnspecified]), the [Grid] layout will automatically place the item based on the + * configured [GridFlow]. + * + * @param row The specific 1-based row index to place the item in. Positive values count from + * the start (1 is the first row). Negative values count from the end (-1 is the last row). + * Must be within the range [-[MaxGridIndex], [MaxGridIndex]]. Defaults to + * [GridIndexUnspecified] for auto-placement. + * @param column The specific 1-based column index to place the item in. Positive values count + * from the start (1 is the first column). Negative values count from the end (-1 is the last + * column). Must be within the range [-[MaxGridIndex], [MaxGridIndex]]. Defaults to + * [GridIndexUnspecified] for auto-placement. + * @param rowSpan The number of rows this item should occupy. Must be greater than 0. Defaults + * to 1. + * @param columnSpan The number of columns this item should occupy. Must be greater than 0. + * Defaults to 1. + * @param alignment Specifies how the content should be aligned within the grid cell(s) it + * occupies. Defaults to [Alignment.TopStart]. + * @throws IllegalArgumentException if [row] or [column] (when specified) are outside the valid + * range, or if [rowSpan] or [columnSpan] are less than 1. + * @see GridIndexUnspecified + * @see MaxGridIndex + */ + @Stable + fun Modifier.gridItem( + row: Int = GridIndexUnspecified, + column: Int = GridIndexUnspecified, + rowSpan: Int = 1, + columnSpan: Int = 1, + alignment: Alignment = Alignment.TopStart, + ): Modifier + + /** + * Configures the position, span, and alignment of an element within a [Grid] layout using + * ranges. + * + * This convenience overload converts [IntRange] inputs into row/column indices and spans. + * + * **Equivalence:** + * - `rows = 4..5` maps to `row = 4`, `rowSpan = 2`. + * - `columns = 1..1` maps to `column = 1`, `columnSpan = 1`. + * + * Example: `Modifier.gridItem(rows = 2..3, columns = 1..2)` is functionally equivalent to + * `Modifier.gridItem(row = 2, rowSpan = 2, column = 1, columnSpan = 2)`. + * + * @param rows The range of rows to occupy (e.g., `1..2`). The start determines the row index, + * and the size of the range determines the span. + * @param columns The range of columns to occupy (e.g., `1..3`). The start determines the column + * index, and the size of the range determines the span. + * @param alignment Specifies how the content should be aligned within the grid cell(s). + * Defaults to [Alignment.TopStart]. + * @see Modifier.gridItem + */ + @Stable + fun Modifier.gridItem( + rows: IntRange, + columns: IntRange, + alignment: Alignment = Alignment.TopStart, + ): Modifier + + companion object { + /** + * The maximum allowed index for a row or column (inclusive). + * + * This hard limit prevents performance degradation, layout timeouts, or memory issues + * potentially caused by accidental loop overflows or unreasonably large sparse grid + * definitions. + */ + const val MaxGridIndex: Int = 1000 + /** + * Sentinel value indicating that a grid position (row or column) is not manually specified + * and should be determined automatically by the layout flow. + */ + const val GridIndexUnspecified: Int = 0 + } +} + +/** Internal implementation of [GridScope]. Stateless object to avoid allocations. */ +@PublishedApi +internal object GridScopeInstance : GridScope { + + override fun Modifier.gridItem( + row: Int, + column: Int, + rowSpan: Int, + columnSpan: Int, + alignment: Alignment, + ): Modifier { + if (row != GridIndexUnspecified) { + require(row in -MaxGridIndex..MaxGridIndex) { + "row must be between -$MaxGridIndex and $MaxGridIndex" + } + } + if (column != GridIndexUnspecified) { + require(column in -MaxGridIndex..MaxGridIndex) { + "column must be between -$MaxGridIndex and $MaxGridIndex" + } + } + require(rowSpan > 0) { "rowSpan must be > 0" } + require(columnSpan > 0) { "columnSpan must be > 0" } + return this.then(GridItemElement(row, column, rowSpan, columnSpan, alignment)) + } + + override fun Modifier.gridItem( + rows: IntRange, + columns: IntRange, + alignment: Alignment, + ): Modifier { + require(!rows.isEmpty()) { "Row range ($rows) cannot be empty" } + require(!columns.isEmpty()) { "Column range ($columns) cannot be empty" } + + val row = rows.first + val rowSpan = rows.last - rows.first + 1 + val column = columns.first + val columnSpan = columns.last - columns.first + 1 + return this.gridItem(row, column, rowSpan, columnSpan, alignment) + } +} + +/** + * Scope for configuring the structure of a [Grid]. + * + * This interface is implemented by the configuration block in [Grid]. It allows defining columns, + * rows, and gaps. + */ +@LayoutScopeMarker +interface GridConfigurationScope : Density { + + /** + * The direction in which items that do not specify a position are placed. Defaults to + * [GridFlow.Row]. + */ + var flow: GridFlow + + /** Defines a fixed-width column. Maps to [GridTrackSize.Fixed]. */ + fun column(size: Dp) + + /** Defines a flexible column. Maps to [GridTrackSize.Flex]. */ + fun column(weight: Fr) + + /** Defines a percentage-based column. Maps to [GridTrackSize.Percentage]. */ + fun column(percentage: Float) + + /** Defines a new column track with the specified [size]. */ + fun column(size: GridTrackSize) + + /** Defines a fixed-width row. Maps to [GridTrackSize.Fixed]. */ + fun row(size: Dp) + + /** Defines a flexible row. Maps to [GridTrackSize.Flex]. */ + fun row(weight: Fr) + + /** Defines a percentage-based row. Maps to [GridTrackSize.Percentage]. */ + fun row(percentage: Float) + + /** Defines a new row track with the specified [size]. */ + fun row(size: GridTrackSize) + + /** + * Sets both the row and column gaps (gutters) to [all]. + * + * **Precedence:** If this is called multiple times, or mixed with [columnGap] or [rowGap], the + * **last call** takes precedence. + * + * @throws IllegalArgumentException if [all] is negative. + */ + fun gap(all: Dp) + + /** + * Sets independent gaps for rows and columns. + * + * **Precedence:** If this is called multiple times, or mixed with [columnGap] or [rowGap], the + * **last call** takes precedence. + * + * @throws IllegalArgumentException if [row] or [column] is negative. + */ + fun gap(row: Dp, column: Dp) + + /** + * Sets the gap (gutter) size between columns. + * + * **Precedence:** If this is called multiple times, the **last call** takes precedence. This + * call will overwrite the column component of any previous [gap] call. + * + * @throws IllegalArgumentException if [gap] is negative. + */ + fun columnGap(gap: Dp) + + /** + * Sets the gap (gutter) size between rows. + * + * **Precedence:** If this is called multiple times, the **last call** takes precedence. This + * call will overwrite the row component of any previous [gap] call. + * + * @throws IllegalArgumentException if [gap] is negative. + */ + fun rowGap(gap: Dp) + + /** Creates an [Fr] unit from an [Int]. */ + @Stable + val Int.fr: Fr + get() = Fr(this.toFloat()) + + /** Creates an [Fr] unit from a [Float]. */ + @Stable + val Float.fr: Fr + get() = Fr(this) + + /** Creates an [Fr] unit from a [Double]. */ + @Stable + val Double.fr: Fr + get() = Fr(this.toFloat()) +} + +/** Adds multiple columns with the specified [specs]. */ +fun GridConfigurationScope.columns(vararg specs: GridTrackSpec) { + for (spec in specs) { + if (spec is GridTrackSize) { + column(spec) + } + } +} + +/** Adds multiple rows with the specified [specs]. */ +fun GridConfigurationScope.rows(vararg specs: GridTrackSpec) { + for (spec in specs) { + if (spec is GridTrackSize) { + row(spec) + } + } +} + +/** Defines the direction in which auto-placed items flow within the grid. */ +@JvmInline +value class GridFlow @PublishedApi internal constructor(private val bits: Int) { + + companion object { + /** Items are placed filling the first row, then moving to the next row. */ + inline val Row + get() = GridFlow(0) + + /** Items are placed filling the first column, then moving to the next column. */ + inline val Column + get() = GridFlow(1) + } + + override fun toString(): String = + when (this) { + Row -> "Row" + Column -> "Column" + else -> "GridFlow($bits)" + } +} + +/** + * Represents a flexible unit used for sizing [Grid] tracks. + * + * One [Fr] unit represents a fraction of the *remaining* space in the grid container after + * [GridTrackSize.Fixed] and [GridTrackSize.Percentage] tracks have been allocated. + */ +@JvmInline +value class Fr(val value: Float) { + override fun toString(): String = "$value.fr" +} + +/** + * Marker interface to enable vararg usage with [GridTrackSize]. + * + * This allows the configuration DSL to accept [GridTrackSize] items in a vararg (e.g., + * `columns(Fixed(10.dp), Flex(1.fr))`), bypassing the Kotlin limitation on value class varargs. + */ +sealed interface GridTrackSpec + +/** + * Defines the size of a track (a row or a column) in a [Grid]. + * + * Use the companion functions (e.g., [Fixed], [Flex]) to create instances. + */ +@Immutable +@JvmInline +value class GridTrackSize internal constructor(internal val encodedValue: Long) : GridTrackSpec { + + internal val type: Int + get() = (encodedValue ushr 32).toInt() + + internal val value: Float + get() = Float.fromBits(encodedValue.toInt()) + + override fun toString(): String = + when (type) { + TypeFixed -> "Fixed(${value}dp)" + TypePercentage -> "Percentage($value)" + TypeFlex -> "Flex(${value}fr)" + TypeMinContent -> "MinContent" + TypeMaxContent -> "MaxContent" + TypeAuto -> "Auto" + else -> "Unknown" + } + + companion object { + internal const val TypeFixed = 1 + internal const val TypePercentage = 2 + internal const val TypeFlex = 3 + internal const val TypeMinContent = 4 + internal const val TypeMaxContent = 5 + internal const val TypeAuto = 6 + + /** + * A track with a fixed [Dp] size. + * + * @param size The size of the track. + * @throws IllegalArgumentException if [size] is negative or [Dp.Unspecified]. + */ + @Stable + fun Fixed(size: Dp): GridTrackSize { + require(size != Dp.Unspecified && size.value >= 0f) { + "Fixed size must be non-negative and specified (was $size)" + } + return pack(TypeFixed, size.value) + } + + /** + * A track sized as a percentage of the **total** available size of the grid container. + * **Note:** In this implementation, percentages are calculated based on the **remaining + * available space after gaps**. This differs from the W3C CSS Grid spec, where percentages + * are based on the container size regardless of gaps. This behavior prevents unexpected + * overflows when mixing gaps and percentages (e.g., `50%` + `50%` + `gap` will fit + * perfectly here, but would overflow in CSS). + * + * @param value The percentage of the container size. + * @throws IllegalArgumentException if [value] is negative. + */ + @Stable + fun Percentage(@FloatRange(from = 0.0) value: Float): GridTrackSize { + require(value >= 0f) { "Percentage cannot be negative" } + return pack(TypePercentage, value) + } + + /** + * A flexible track that takes a share of the **remaining** space after Fixed and Percentage + * tracks are allocated. + * + * @param weight The flexible weight. Space is distributed proportional to this weight + * divided by the total flex weight. Must be non-negative. + * @throws IllegalArgumentException if [weight] is negative. + */ + @Stable + fun Flex(@FloatRange(from = 0.0) weight: Fr): GridTrackSize { + require(weight.value >= 0f) { "Flex weight must be positive" } + return pack(TypeFlex, weight.value) + } + + /** A track that sizes itself to fit the minimum intrinsic size of its contents. */ + @Stable val MinContent = pack(TypeMinContent, 0f) + + /** A track that sizes itself to fit the maximum intrinsic size of its contents. */ + @Stable val MaxContent = pack(TypeMaxContent, 0f) + + /** + * A track that behaves automatically, typically similar to [MinContent] or [Flex] depending + * on context. + */ + @Stable val Auto = pack(TypeAuto, 0f) + + private fun pack(type: Int, value: Float): GridTrackSize { + // Pack Type (High 32) and Float bits (Low 32) into one Long. + // Mask 0xFFFFFFFFL prevents sign extension when casting int to long. + val raw = (type.toLong() shl 32) or (value.toRawBits().toLong() and 0xFFFFFFFFL) + return GridTrackSize(raw) + } + } +} + +/** + * The modifier element that creates and updates [GridItemNode]. + * + * @property row The 1-based row index, or [GridScope.GridIndexUnspecified] for auto-placement. + * @property column The 1-based column index, or [GridScope.GridIndexUnspecified] for + * auto-placement. + * @property rowSpan The number of rows the item should occupy. + * @property columnSpan The number of columns the item should occupy. + * @property alignment The alignment of the content within the grid cell. + * @see GridItemNode + */ +private class GridItemElement( + val row: Int, + val column: Int, + val rowSpan: Int, + val columnSpan: Int, + val alignment: Alignment, +) : ModifierNodeElement() { + override fun create(): GridItemNode = GridItemNode(row, column, rowSpan, columnSpan, alignment) + + override fun update(node: GridItemNode) { + node.row = row + node.column = column + node.rowSpan = rowSpan + node.columnSpan = columnSpan + node.alignment = alignment + } + + override fun InspectorInfo.inspectableProperties() { + name = "gridItem" + properties["row"] = row + properties["column"] = column + properties["rowSpan"] = rowSpan + properties["columnSpan"] = columnSpan + properties["alignment"] = alignment + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GridItemElement) return false + + if (row != other.row) return false + if (column != other.column) return false + if (rowSpan != other.rowSpan) return false + if (columnSpan != other.columnSpan) return false + if (alignment != other.alignment) return false + + return true + } + + override fun hashCode(): Int { + var result = row + result = 31 * result + column + result = 31 * result + rowSpan + result = 31 * result + columnSpan + result = 31 * result + alignment.hashCode() + return result + } +} + +/** + * The modifier node that provides parent data to the [Grid] layout. + * + * This class implements [ParentDataModifierNode], allowing the parent [Grid] layout to inspect the + * configuration (row, column, spans) of this specific child during the measurement phase via the + * [modifyParentData] method. + * + * @property row The 1-based row index, or [GridScope.GridIndexUnspecified] for auto-placement. + * @property column The 1-based column index, or [GridScope.GridIndexUnspecified] for + * auto-placement. + * @property rowSpan The number of rows the item should occupy. + * @property columnSpan The number of columns the item should occupy. + * @property alignment The alignment of the content within the grid cell. + * @throws IllegalArgumentException if [rows] or [columns] ranges are empty, or if the derived + * row/column indices or spans do not meet the requirements of the primary [gridItem] function. + * @see GridScope.gridItem for the public API and input validation. + */ +private class GridItemNode( + var row: Int, + var column: Int, + var rowSpan: Int, + var columnSpan: Int, + var alignment: Alignment, +) : Modifier.Node(), ParentDataModifierNode { + override fun Density.modifyParentData(parentData: Any?) = this@GridItemNode +} From 092e36a8891ac398b952fadc648a1be03ef1c9bf Mon Sep 17 00:00:00 2001 From: Prashant Date: Wed, 10 Dec 2025 03:52:59 +0000 Subject: [PATCH 04/19] Implement Grid sizing and explicit placement This change implements the core layout logic for the `Grid` composable, enabling it to measure and place children based on explicit configuration. Key features implemented: - **Track Sizing:** Handles `Fixed`, `Percentage`, and `Flex` track sizes for both columns and rows. The calculation process prioritizes column widths to correctly inform row height calculations, especially for content like text that wraps. Flex tracks distribute remaining space after considering content minimums. - **Explicit Placement:** Children tagged with the `Modifier.gridItem()` and providing specific row/column values are now placed at their designated locations. - **Spanning Support:** The sizing logic accounts for items spanning multiple tracks, ensuring tracks expand as needed to accommodate the content of spanning items. - **Constraint Handling:** The grid layout respects incoming min/max constraints from the parent layout. RelNote: "Implemented core layout logic for Grid, enabling explicit placement and support for Fixed, Flex, and Percentage sizing." Bug: 462550392 Test: GridTest Change-Id: I7d3c9578e82b996c7f9d7bc4e1f6a110e8cd426f --- .../api/restricted_current.txt | 6 + .../foundation-layout/bcv/native/current.txt | 6 + .../foundation/layout/demos/GridDemo.kt | 181 +++ .../foundation/layout/demos/LayoutDemos.kt | 1 + .../compose/foundation/layout/GridTest.kt | 1160 +++++++++++++++++ .../compose/foundation/layout/Grid.kt | 1001 +++++++++++++- 6 files changed, 2350 insertions(+), 5 deletions(-) create mode 100644 compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt create mode 100644 compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt index 64d496f56abee..b7ba6fb8ff5e6 100644 --- a/compose/foundation/foundation-layout/api/restricted_current.txt +++ b/compose/foundation/foundation-layout/api/restricted_current.txt @@ -363,6 +363,12 @@ package androidx.compose.foundation.layout { method public static void rows(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); } + @kotlin.PublishedApi internal final class GridMeasurePolicy implements androidx.compose.ui.layout.MeasurePolicy { + ctor public GridMeasurePolicy(androidx.compose.runtime.State> configState); + method @KotlinOnly public androidx.compose.ui.layout.MeasureResult measure(androidx.compose.ui.layout.MeasureScope, java.util.List measurables, androidx.compose.ui.unit.Constraints constraints); + method @BytecodeOnly public androidx.compose.ui.layout.MeasureResult measure-3p2s80s(androidx.compose.ui.layout.MeasureScope, java.util.List, long); + } + @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable @kotlin.jvm.JvmDefaultWithCompatibility public interface GridScope { method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, optional int row, optional int column, optional int rowSpan, optional int columnSpan, optional androidx.compose.ui.Alignment alignment); method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, kotlin.ranges.IntRange rows, kotlin.ranges.IntRange columns, optional androidx.compose.ui.Alignment alignment); diff --git a/compose/foundation/foundation-layout/bcv/native/current.txt b/compose/foundation/foundation-layout/bcv/native/current.txt index f6e98943a2843..7762ea02494b8 100644 --- a/compose/foundation/foundation-layout/bcv/native/current.txt +++ b/compose/foundation/foundation-layout/bcv/native/current.txt @@ -135,6 +135,12 @@ abstract interface androidx.compose.foundation.layout/WindowInsets { // androidx sealed interface androidx.compose.foundation.layout/GridTrackSpec // androidx.compose.foundation.layout/GridTrackSpec|null[0] +final class androidx.compose.foundation.layout/GridMeasurePolicy : androidx.compose.ui.layout/MeasurePolicy { // androidx.compose.foundation.layout/GridMeasurePolicy|null[0] + constructor (androidx.compose.runtime/State>) // androidx.compose.foundation.layout/GridMeasurePolicy.|(androidx.compose.runtime.State>){}[0] + + final fun (androidx.compose.ui.layout/MeasureScope).measure(kotlin.collections/List, androidx.compose.ui.unit/Constraints): androidx.compose.ui.layout/MeasureResult // androidx.compose.foundation.layout/GridMeasurePolicy.measure|measure@androidx.compose.ui.layout.MeasureScope(kotlin.collections.List;androidx.compose.ui.unit.Constraints){}[0] +} + final value class androidx.compose.foundation.layout/Fr { // androidx.compose.foundation.layout/Fr|null[0] constructor (kotlin/Float) // androidx.compose.foundation.layout/Fr.|(kotlin.Float){}[0] diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt new file mode 100644 index 0000000000000..9dbc5059e743c --- /dev/null +++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt @@ -0,0 +1,181 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.compose.foundation.layout.demos + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Grid +import androidx.compose.foundation.layout.GridScope +import androidx.compose.foundation.layout.GridTrackSize +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.columns +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun GridDemo() { + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(16.dp)) { + MixedSizingDemo() + Spacer(Modifier.height(32.dp)) + FlexibleSizingDemo() + Spacer(Modifier.height(32.dp)) + NegativeIndicesDemo() + Spacer(Modifier.height(32.dp)) + } +} + +@Composable +private fun MixedSizingDemo() { + DemoHeader("Mixed Sizing: Fixed vs Fraction vs Flex") + Grid( + config = { + columns( + GridTrackSize.Fixed(100.dp), + GridTrackSize.Percentage(0.3f), + GridTrackSize.Flex(1.fr), + ) + row(100.dp) + }, + modifier = Modifier.demoContainer(), + ) { + GridDemoItem(text = "Fixed\n100dp", color = Color.Red, row = 1, column = 1) + GridDemoItem(text = "Fraction\n30% of Total", color = Color.Blue, row = 1, column = 2) + GridDemoItem(text = "Flex\nRest of Space", color = Color.Green, row = 1, column = 3) + } +} + +@Composable +private fun FlexibleSizingDemo() { + DemoHeader("Flexible (Fr) Sizing") + Grid( + config = { + column(80.dp) + column(1.fr) + column(2.fr) + row(1.fr) + row(60.dp) + }, + modifier = Modifier.height(200.dp).demoContainer(borderColor = Color.Magenta), + ) { + val rowLabels = listOf("Flex 1.fr", "60dp") + val colLabels = listOf("Fixed 80dp", "Flex 1.fr", "Flex 2.fr") + + rowLabels.forEachIndexed { rowIndex, rowLabel -> + colLabels.forEachIndexed { colIndex, colLabel -> + GridDemoItem( + text = "H: $rowLabel\nW: $colLabel", + row = rowIndex + 1, + column = colIndex + 1, + ) + } + } + } +} + +@Composable +fun NegativeIndicesDemo() { + DemoHeader("Negative Indices") + Grid( + config = { + repeat(3) { column(60.dp) } + repeat(3) { row(60.dp) } + gap(4.dp) + }, + modifier = Modifier.border(1.dp, Color.Gray), + ) { + GridDemoItem(text = "TL", row = 1, column = 1, color = Color.Red) + GridDemoItem("TR", row = 1, column = -1, color = Color.Blue) + GridDemoItem("BL", row = -1, column = 1, color = Color.Green) + GridDemoItem("BR", row = -1, column = -1, color = Color.Yellow) + GridDemoItem("Center", row = 2, column = 2, color = Color.Gray) + } +} + +@Composable +private fun DemoHeader(text: String) = + Text( + text, + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(bottom = 8.dp), + ) + +@Composable +private fun GridScope.GridDemoItem( + text: String, + modifier: Modifier = Modifier, + row: Int? = null, + column: Int? = null, + rowSpan: Int = 1, + columnSpan: Int = 1, + color: Color = Color.Green, + measureSize: Boolean = true, +) { + var size by remember { mutableStateOf(IntSize.Zero) } + val density = LocalDensity.current + var finalModifier = modifier.fillMaxSize() + + if (row != null && column != null) { + finalModifier = finalModifier.gridItem(row, column, rowSpan, columnSpan) + } else if (rowSpan > 1 || columnSpan > 1) { + finalModifier = finalModifier.gridItem(rowSpan = rowSpan, columnSpan = columnSpan) + } + + Box( + finalModifier + .onSizeChanged { size = it } + .background(color.copy(alpha = 0.1f)) + .border(1.dp, color.copy(alpha = 0.5f)) + .padding(2.dp), + contentAlignment = Alignment.Center, + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text(text = text, fontSize = 10.sp, textAlign = TextAlign.Center) + if (measureSize) { + val w = with(density) { size.width.toDp().value.toInt() } + val h = with(density) { size.height.toDp().value.toInt() } + Text(text = "$w x $h", fontSize = 9.sp, fontWeight = FontWeight.Bold) + } + } + } +} + +private fun Modifier.demoContainer(borderColor: Color = Color.Black) = + this.fillMaxWidth().border(1.dp, borderColor.copy(alpha = 0.5f)).padding(8.dp) diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt index 18ed55e40f433..c348dfe2d5e9c 100644 --- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt +++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt @@ -29,5 +29,6 @@ val LayoutDemos = ComposableDemo("Contextual Flow Row") { ContextualFlowRowDemo() }, ComposableDemo("Contextual FlowColumn") { ContextualFlowColumnDemo() }, ComposableDemo("Rtl support") { RtlDemo() }, + ComposableDemo("Grid") { GridDemo() }, ), ) diff --git a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt new file mode 100644 index 0000000000000..3764edf0c7e31 --- /dev/null +++ b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt @@ -0,0 +1,1160 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.foundation.layout + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.layout.IntrinsicMeasurable +import androidx.compose.ui.layout.IntrinsicMeasureScope +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasurePolicy +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.node.Ref +import androidx.compose.ui.platform.isDebugInspectorInfoEnabled +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import kotlin.math.roundToInt +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class GridTest : LayoutTest() { + + @Before + fun before() { + isDebugInspectorInfoEnabled = true + } + + @After + fun after() { + isDebugInspectorInfoEnabled = false + } + + @Test + fun testGrid_itemModifierChange_triggersRelayout() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + var targetRow by mutableStateOf(1) + + // Latch for initial layout (Row 1) + val initialLatch = CountDownLatch(1) + // Latch for update layout (Row 2) + val updateLatch = CountDownLatch(1) + + val childPosition = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(sizeDp)) + row(GridTrackSize.Fixed(sizeDp)) + row(GridTrackSize.Fixed(sizeDp)) + } + ) { + Box( + Modifier.gridItem(row = targetRow, column = 1) + .size(sizeDp) + .onGloballyPositioned { coordinates -> + childPosition.value = coordinates.localToRoot(Offset.Zero) + if (initialLatch.count > 0) { + initialLatch.countDown() + } else { + updateLatch.countDown() + } + } + ) + } + } + + // 1. Verify Initial Position (Row 1 -> Index 0 -> 0px) + assertTrue( + "Timed out waiting for initial layout", + initialLatch.await(1, TimeUnit.SECONDS), + ) + assertEquals(Offset(0f, 0f), childPosition.value) + + // 2. Update State + targetRow = 2 + + // 3. Verify Updated Position (Row 2 -> Index 1 -> 50px) + assertTrue( + "Timed out waiting for layout update", + updateLatch.await(1, TimeUnit.SECONDS), + ) + assertEquals(Offset(0f, size.toFloat()), childPosition.value) + } + + @Test + fun testGrid_configStateChange_updatesLayout() = + with(density) { + var trackSize by mutableStateOf(50.dp) + + // Use separate latches for the initial pass and the update pass. + val initialLayoutLatch = CountDownLatch(1) + val updateLayoutLatch = CountDownLatch(1) + + val childSize = Ref() + + show { + Grid( + config = { + // Reading 'trackSize' here registers a dependency. + // When 'trackSize' changes, this lambda re-executes, triggering + // the MeasurePolicy to re-measure. + column(GridTrackSize.Fixed(trackSize)) + row(GridTrackSize.Fixed(trackSize)) + } + ) { + Box( + Modifier.gridItem(1, 1).fillMaxSize().onGloballyPositioned { coordinates -> + childSize.value = coordinates.size + + // If the first latch is still open, this is the initial layout. + // Otherwise, we are in the update phase. + if (initialLayoutLatch.count > 0) { + initialLayoutLatch.countDown() + } else { + updateLayoutLatch.countDown() + } + } + ) + } + } + + // Verify Initial State (50.dp) + // Wait for the FIRST layout pass to complete + assertTrue( + "Timed out waiting for initial layout", + initialLayoutLatch.await(1, TimeUnit.SECONDS), + ) + assertEquals( + "Initial size incorrect", + IntSize(50.dp.roundToPx(), 50.dp.roundToPx()), + childSize.value, + ) + + // Change State + trackSize = 100.dp + + // Verify Update (100.dp) + // Wait for the SECOND layout pass (recomposition) to complete + assertTrue( + "Timed out waiting for layout update", + updateLayoutLatch.await(1, TimeUnit.SECONDS), + ) + assertEquals( + "Grid did not update track size after config state changed", + IntSize(100.dp.roundToPx(), 100.dp.roundToPx()), + childSize.value, + ) + } + + @Test + fun testGrid_fixedTracks_sizeCorrectly() = + with(density) { + val size1 = 50 + val size2 = 100 + val size1Dp = size1.toDp() + val size2Dp = size2.toDp() + + val positionedLatch = CountDownLatch(2) + val childSize = Array(2) { Ref() } + val childPosition = Array(2) { Ref() } + + show { + Grid( + config = { + column(GridTrackSize.Fixed(size1Dp)) + column(GridTrackSize.Fixed(size2Dp)) + row(GridTrackSize.Fixed(size1Dp)) + } + ) { + // R1, C1 + Box( + Modifier.gridItem(1, 1) + .fillMaxSize() + .saveLayoutInfo(childSize[0], childPosition[0], positionedLatch) + ) + // R1, C2 + Box( + Modifier.gridItem(1, 2) + .fillMaxSize() + .saveLayoutInfo(childSize[1], childPosition[1], positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + // Check Item 1 (50x50) at (0,0) + assertEquals(IntSize(size1, size1), childSize[0].value) + assertEquals(Offset(0f, 0f), childPosition[0].value) + + // Check Item 2 (100x50) at (50,0) + assertEquals(IntSize(size2, size1), childSize[1].value) + assertEquals(Offset(size1.toFloat(), 0f), childPosition[1].value) + } + + @Test + fun testGrid_fractionTracks() = + with(density) { + val totalSize = 200 + val totalSizeDp = totalSize.toDp() + + // Col 1: 25% = 50px + // Col 2: 75% = 150px + val expectedCol1 = (totalSize * 0.25f).roundToInt() + val expectedCol2 = (totalSize * 0.75f).roundToInt() + + val positionedLatch = CountDownLatch(2) + val childSize = Array(2) { Ref() } + val childPosition = Array(2) { Ref() } + + show { + Grid( + config = { + column(GridTrackSize.Percentage(0.25f)) + column(GridTrackSize.Percentage(0.75f)) + row(GridTrackSize.Fixed(50.dp)) + }, + modifier = Modifier.size(totalSizeDp, 50.dp), + ) { + Box( + Modifier.gridItem(1, 1) + .fillMaxSize() + .saveLayoutInfo(childSize[0], childPosition[0], positionedLatch) + ) + Box( + Modifier.gridItem(1, 2) + .fillMaxSize() + .saveLayoutInfo(childSize[1], childPosition[1], positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + assertEquals(IntSize(expectedCol1, 50.dp.roundToPx()), childSize[0].value) + assertEquals(Offset(0f, 0f), childPosition[0].value) + + assertEquals(IntSize(expectedCol2, 50.dp.roundToPx()), childSize[1].value) + assertEquals(Offset(expectedCol1.toFloat(), 0f), childPosition[1].value) + } + + @Test + fun testGrid_percentageRows_resolvesAgainstHeight() = + with(density) { + // Scenario: + // Fixed Height Container (200px). + // Row 1: 25% (50px) + // Row 2: 75% (150px) + + val totalHeight = 200 + val expectedRow1 = (totalHeight * 0.25f).roundToInt() + val expectedRow2 = (totalHeight * 0.75f).roundToInt() + + val latch = CountDownLatch(2) + val sizes = Array(2) { Ref() } + + show { + Grid( + config = { + column(GridTrackSize.Fixed(50.dp)) + row(GridTrackSize.Percentage(0.25f)) + row(GridTrackSize.Percentage(0.75f)) + }, + modifier = Modifier.height(totalHeight.toDp()), + ) { + // Item in Row 1 + Box( + Modifier.gridItem(1, 1).fillMaxSize().saveLayoutInfo(sizes[0], Ref(), latch) + ) + // Item in Row 2 + Box( + Modifier.gridItem(2, 1).fillMaxSize().saveLayoutInfo(sizes[1], Ref(), latch) + ) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(expectedRow1, sizes[0].value?.height) + assertEquals(expectedRow2, sizes[1].value?.height) + } + + @Test + fun testGrid_flexTracks() = + with(density) { + val totalWidth = 300 + val fixedWidth = 100 + + // Remaining space = 200 + // Flex 1: 1fr = 200 * (1/4) = 50 + // Flex 2: 3fr = 200 * (3/4) = 150 + + val positionedLatch = CountDownLatch(3) + val childSize = Array(3) { Ref() } + val childPosition = Array(3) { Ref() } + + show { + Grid( + config = { + column(GridTrackSize.Fixed(fixedWidth.toDp())) + column(GridTrackSize.Flex(1.fr)) + column(GridTrackSize.Flex(3.fr)) + row(GridTrackSize.Fixed(50.dp)) + }, + modifier = Modifier.width(totalWidth.toDp()), + ) { + Box( + Modifier.gridItem(1, 1) + .fillMaxSize() + .saveLayoutInfo(childSize[0], childPosition[0], positionedLatch) + ) + Box( + Modifier.gridItem(1, 2) + .fillMaxSize() + .saveLayoutInfo(childSize[1], childPosition[1], positionedLatch) + ) + Box( + Modifier.gridItem(1, 3) + .fillMaxSize() + .saveLayoutInfo(childSize[2], childPosition[2], positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + // Fixed Col + assertEquals(IntSize(fixedWidth, 50.dp.roundToPx()), childSize[0].value) + assertEquals(Offset(0f, 0f), childPosition[0].value) + + // Flex 1 (50px) + assertEquals(IntSize(50, 50.dp.roundToPx()), childSize[1].value) + assertEquals(Offset(fixedWidth.toFloat(), 0f), childPosition[1].value) + + // Flex 3 (150px) + assertEquals(IntSize(150, 50.dp.roundToPx()), childSize[2].value) + assertEquals(Offset((fixedWidth + 50).toFloat(), 0f), childPosition[2].value) + } + + @Test + fun testGrid_flexTrack_minContent_lowerBound() = + with(density) { + val containerWidth = 50 + val itemMinSize = 100 + val latch = CountDownLatch(1) + val childSize = Ref() + + show { + // Container is 50px wide, but item needs 100px. + // Flex track should expand to 100px (min-content), ignoring the 50px constraint. + Box(Modifier.width(containerWidth.toDp())) { + Grid( + config = { + column(GridTrackSize.Flex(1.fr)) + row(GridTrackSize.Fixed(50.dp)) + } + ) { + IntrinsicItem( + minWidth = itemMinSize, + minIntrinsicWidth = itemMinSize, + maxIntrinsicWidth = itemMinSize, + modifier = + Modifier.gridItem(1, 1) + .fillMaxHeight() + .saveLayoutInfo(childSize, Ref(), latch), + ) + } + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals( + "Flex track should not shrink below min-content size", + itemMinSize, + childSize.value?.width, + ) + } + + @Test + fun testGrid_flexTracks_distributeSpace_afterMinContent() = + with(density) { + // Scenario: + // Container = 200px + // Track 1 (1fr): Has large content (150px) + // Track 2 (1fr): Has small content (10px) + // + // Calculation: + // 1. Base Sizes (Pass 1): + // Track 1 = 150px + // Track 2 = 10px + // Used = 160px + // + // 2. Remaining Space (Pass 2): + // 200px - 160px = 40px + // + // 3. Distribution (Equal weight 1fr): + // Track 1 adds 20px -> Final 170px + // Track 2 adds 20px -> Final 30px + + val containerWidth = 200 + val item1MinSize = 150 + val item2MinSize = 10 + val latch = CountDownLatch(2) + val size1 = Ref() + val size2 = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Flex(1.fr)) + column(GridTrackSize.Flex(1.fr)) + row(GridTrackSize.Fixed(50.dp)) + }, + modifier = Modifier.width(containerWidth.toDp()), + ) { + // Item 1: Large Min Content + IntrinsicItem( + minWidth = item1MinSize, + minIntrinsicWidth = item1MinSize, + maxIntrinsicWidth = item1MinSize, + modifier = + Modifier.gridItem(1, 1) + .fillMaxWidth() // <--- ADD THIS + .saveLayoutInfo(size1, Ref(), latch), + ) + + // Item 2: Small Min Content + IntrinsicItem( + minWidth = item2MinSize, + minIntrinsicWidth = item2MinSize, + maxIntrinsicWidth = item2MinSize, + modifier = + Modifier.gridItem(1, 2) + .fillMaxWidth() // <--- ADD THIS + .saveLayoutInfo(size2, Ref(), latch), + ) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals("Track 1 should be Base(150) + Share(20)", 170, size1.value?.width) + assertEquals("Track 2 should be Base(10) + Share(20)", 30, size2.value?.width) + } + + @Test + fun testGrid_rowSpan_expandsRowsToFitContent() = + with(density) { + // Scenario: + // 2 Columns (Fixed 50) + // 2 Rows (Auto) + // Item 1 (Col 1, Row 1): Small (10px height) + // Item 2 (Col 1, Row 2): Small (10px height) + // Item 3 (Col 2, Row 1, Span 2): Tall (100px height) + // + // Expected Behavior: + // Without spanning logic: Rows would be 10px each (total 20px). Item 3 would overflow. + // With spanning logic: Item 3 needs 100px. + // Deficit = 100 - (10 + 10) = 80px. + // Distribute 80px / 2 rows = +40px each. + // Final Row Heights: 10 + 40 = 50px each. + // Total Grid Height: 100px. + + val colWidth = 50.dp + val smallItemHeight = 10.dp + val tallItemHeight = 100.dp + val expectedTotalHeight = 100.dp.roundToPx() + + val latch = CountDownLatch(1) + val gridSize = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(colWidth)) + column(GridTrackSize.Fixed(colWidth)) + row(GridTrackSize.Auto) + row(GridTrackSize.Auto) + }, + modifier = + Modifier.onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + // Col 1, Row 1 + Box(Modifier.gridItem(1, 1).size(colWidth, smallItemHeight)) + // Col 1, Row 2 + Box(Modifier.gridItem(2, 1).size(colWidth, smallItemHeight)) + + // Col 2, Row 1, Span 2 (The driver of expansion) + Box( + Modifier.gridItem(row = 1, column = 2, rowSpan = 2) + .size(colWidth, tallItemHeight) + ) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals( + "Grid height should expand to accommodate the tall row-spanning item", + expectedTotalHeight, + gridSize.value?.height, + ) + } + + @Test + fun testGrid_rowSpan_expandsFlexRows() = + with(density) { + // Scenario: + // Container Height = 100px (Fixed constraint) + // 2 Rows (Flex 1fr) + // Item 1 (Row 1): Empty + // Item 2 (Row 2): Empty + // Item 3 (Span 2): Tall (200px) + // + // Expected Behavior: + // Flex logic initially splits 100px -> 50px each. + // Spanning logic sees Item 3 needs 200px. + // Deficit = 200 - 100 = 100px. + // Rows should grow to 100px each. + // Total Height = 200px (Grid expands beyond parent constraint if content demands it). + + val containerHeight = 100.dp + val tallItemHeight = 200.dp + val expectedTotalHeight = 200.dp.roundToPx() + + val latch = CountDownLatch(1) + val gridSize = Ref() + + show { + // Wrap in a box with fixed height to simulate constraints, + // but allow Grid to be larger (unbounded internal checks) + Box( + Modifier.height(containerHeight) + .wrapContentHeight(align = Alignment.Top, unbounded = true) + ) { + Grid( + config = { + column(GridTrackSize.Fixed(50.dp)) + row(GridTrackSize.Flex(1.fr)) + row(GridTrackSize.Flex(1.fr)) + }, + modifier = + Modifier.onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + // Spanning item forcing expansion + Box( + Modifier.gridItem(row = 1, column = 1, rowSpan = 2) + .size(50.dp, tallItemHeight) + ) + } + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals( + "Flex rows should expand beyond 1fr share if spanning item requires it", + expectedTotalHeight, + gridSize.value?.height, + ) + } + + @Test + fun testGrid_explicitPlacement_allowsOverlaps() = + with(density) { + // Scenario: + // Two items explicitly placed in (1, 1). + // They should occupy the same space. The grid should not throw or shift them. + + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(2) + val pos = Array(2) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(sizeDp)) + row(GridTrackSize.Fixed(sizeDp)) + } + ) { + // Item 1 + Box(Modifier.gridItem(1, 1).size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) + // Item 2 (Same Cell) + Box(Modifier.gridItem(1, 1).size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset(0f, 0f), pos[1].value) + } + + @Test + fun testGrid_negativeIndices_placeCorrectly() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + + val positionedLatch = CountDownLatch(4) + val childPosition = Array(4) { Ref() } + val dummySize = Array(4) { Ref() } + + show { + Grid( + config = { + repeat(3) { column(GridTrackSize.Fixed(sizeDp)) } + repeat(3) { row(GridTrackSize.Fixed(sizeDp)) } + } + ) { + // 1. Top-Left (1, 1) -> (0, 0) + Box( + Modifier.gridItem(1, 1) + .fillMaxSize() + .saveLayoutInfo(dummySize[0], childPosition[0], positionedLatch) + ) + // 2. Top-Right (1, -1) -> (100, 0) (Last Column) + Box( + Modifier.gridItem(1, -1) + .fillMaxSize() + .saveLayoutInfo(dummySize[1], childPosition[1], positionedLatch) + ) + // 3. Bottom-Left (-1, 1) -> (0, 100) (Last Row) + Box( + Modifier.gridItem(-1, 1) + .fillMaxSize() + .saveLayoutInfo(dummySize[2], childPosition[2], positionedLatch) + ) + // 4. Bottom-Right (-1, -1) -> (100, 100) (Last Row, Last Column) + Box( + Modifier.gridItem(-1, -1) + .fillMaxSize() + .saveLayoutInfo(dummySize[3], childPosition[3], positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + // 1. (0, 0) + assertEquals(Offset(0f, 0f), childPosition[0].value) + // 2. (100, 0) -> Col index 2 * 50 + assertEquals(Offset((size * 2).toFloat(), 0f), childPosition[1].value) + // 3. (0, 100) -> Row index 2 * 50 + assertEquals(Offset(0f, (size * 2).toFloat()), childPosition[2].value) + // 4. (100, 100) + assertEquals(Offset((size * 2).toFloat(), (size * 2).toFloat()), childPosition[3].value) + } + + @Test + fun testGrid_invalidNegativeIndices_fallbackToAuto() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(2) + val pos1 = Ref() + val pos2 = Ref() + val dummy = Ref() + + show { + Grid( + config = { + // 2x2 Grid + repeat(2) { column(GridTrackSize.Fixed(sizeDp)) } + repeat(2) { row(GridTrackSize.Fixed(sizeDp)) } + } + ) { + // Case 1: Valid Negative (-1 -> Index 1) + Box( + Modifier.gridItem(row = -1, column = -1) + .size(sizeDp) + .saveLayoutInfo(dummy, pos1, latch) + ) + + // Case 2: Invalid Negative (-5 -> Index -3 -> Invalid) + // Should be treated as "Unspecified" and auto-placed. + // Since (1,1) is empty (Item 1 is at 1,1 0-based), it should go to (0,0). + Box( + Modifier.gridItem(row = -5, column = -5) + .size(sizeDp) + .saveLayoutInfo(dummy, pos2, latch) + ) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Item 1 (Valid -1,-1): Bottom-Right (50, 50) + assertEquals(Offset(size.toFloat(), size.toFloat()), pos1.value) + + // Item 2 (Invalid -5,-5): Auto-placed to first available slot (0,0) + assertEquals(Offset(0f, 0f), pos2.value) + } + + @Test + fun testGrid_spanning() { + val colSize = 50 + val rowSize = 50 + + val positionedLatch = CountDownLatch(1) + val childSize = Ref() + val childPosition = Ref() + + show { + Grid( + config = { + repeat(3) { column(GridTrackSize.Fixed(colSize.toDp())) } + repeat(3) { row(GridTrackSize.Fixed(rowSize.toDp())) } + } + ) { + // Item at R2, C2 spanning 2 rows and 2 columns + // Should be at (50, 50) with size (100, 100) + Box( + Modifier.gridItem(row = 2, column = 2, rowSpan = 2, columnSpan = 2) + .fillMaxSize() + .saveLayoutInfo(childSize, childPosition, positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + assertEquals(IntSize(colSize * 2, rowSize * 2), childSize.value) + assertEquals(Offset(colSize.toFloat(), rowSize.toFloat()), childPosition.value) + } + + @Test + fun testGrid_spanEntireGrid() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(1) + val itemSize = Ref() + + show { + Grid( + config = { + repeat(4) { column(GridTrackSize.Fixed(sizeDp)) } + row(GridTrackSize.Fixed(sizeDp)) + } + ) { + // Spans all 4 columns + Box( + Modifier.gridItem(1, 1, columnSpan = 4) + .fillMaxSize() + .saveLayoutInfo(itemSize, Ref(), latch) + ) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(size * 4, itemSize.value?.width) + } + + @Test + fun testGrid_respectsMinConstraints_expandsToFill() = + with(density) { + val smallTrackSize = 50.dp + val largeParentSize = 100.dp + val expectedSize = 100.dp.roundToPx() + + val positionedLatch = CountDownLatch(1) + val gridSize = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(smallTrackSize)) + row(GridTrackSize.Fixed(smallTrackSize)) + }, + // Force the Grid to be larger than its content + modifier = + Modifier.size(largeParentSize).onGloballyPositioned { coordinates -> + gridSize.value = coordinates.size + positionedLatch.countDown() + }, + ) { /* empty */ + } + } + + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + assertEquals( + "Grid should expand to satisfy min constraints", + IntSize(expectedSize, expectedSize), + gridSize.value, + ) + } + + @Test + fun testGrid_respectsMaxConstraints_coercesSize() = + with(density) { + val largeTrackSize = 200.dp + val smallParentSize = 100.dp + val expectedSize = 100.dp.roundToPx() + + val positionedLatch = CountDownLatch(1) + val gridSize = Ref() + + show { + // Wrap in Box with propagateMinConstraints=false to test pure max constraints + Box(Modifier.size(smallParentSize)) { + Grid( + config = { + column(GridTrackSize.Fixed(largeTrackSize)) + row(GridTrackSize.Fixed(largeTrackSize)) + }, + modifier = + Modifier.onGloballyPositioned { coordinates -> + gridSize.value = coordinates.size + positionedLatch.countDown() + }, + ) { /* empty */ + } + } + } + + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + assertEquals( + "Grid should respect max constraints even if tracks are larger", + IntSize(expectedSize, expectedSize), + gridSize.value, + ) + } + + @Test + fun testGrid_respectsConstraints_whenContentOverflows() = + with(density) { + val parentSize = 100 + val contentSize = 200 // Larger than parent + + val latch = CountDownLatch(1) + val gridSize = Ref() + + show { + // Parent container restricts size to 100x100 + Box(Modifier.size(parentSize.toDp())) { + Grid( + config = { + // Grid wants to be 200x200 + column(GridTrackSize.Fixed(contentSize.toDp())) + row(GridTrackSize.Fixed(contentSize.toDp())) + }, + modifier = + Modifier.onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + Box(Modifier.gridItem(1, 1).fillMaxSize()) + } + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Assert that Grid reported the PARENT'S size (clamped), not the content size + assertEquals( + "Grid should be clamped to parent max width/height", + IntSize(parentSize, parentSize), + gridSize.value, + ) + } + + @Test + fun testGrid_respectsConstraints_whenContentUnderflows() = + with(density) { + val minSize = 200 + val contentSize = 50 // Smaller than parent min + + val latch = CountDownLatch(1) + val gridSize = Ref() + + show { + // Parent enforces minimum size of 200x200 (e.g. fillMaxSize) + Box(Modifier.requiredSize(minSize.toDp())) { + Grid( + config = { + column(GridTrackSize.Fixed(contentSize.toDp())) + row(GridTrackSize.Fixed(contentSize.toDp())) + }, + modifier = + Modifier.fillMaxSize() // Request to fill parent + .onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + Box(Modifier.gridItem(1, 1).fillMaxSize()) + } + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Assert that Grid expanded to meet the minimum constraints + assertEquals( + "Grid should expand to meet min constraints", + IntSize(minSize, minSize), + gridSize.value, + ) + } + + @Test + fun testGrid_percentageTrack_inIndefiniteContainer_fallbacksToAuto() = + with(density) { + val positionedLatch = CountDownLatch(1) + val gridSize = Ref() + + show { + // Wrap in a Row to provide infinite width constraint + Row { + Grid( + config = { + // 50% of Infinity cannot be calculated. + // Fallback to Auto (MaxContent) and fit the item. + column(GridTrackSize.Percentage(0.5f)) + row(GridTrackSize.Fixed(50.dp)) + }, + modifier = + Modifier.onGloballyPositioned { coordinates -> + gridSize.value = coordinates.size + positionedLatch.countDown() + }, + ) { + // The item is 10.dp wide. The track should expand to fit this. + Box(Modifier.gridItem(1, 1).size(10.dp)) + } + } + } + + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + // Width should be 10.dp (Size of the content), NOT 0. + // Height should be 50.dp (Fixed) + assertEquals(IntSize(10.dp.roundToPx(), 50.dp.roundToPx()), gridSize.value) + } + + @Test + fun testGrid_zeroSizeTrack_layoutCorrectly() = + with(density) { + val size = 50 + val latch = CountDownLatch(2) + val pos = Array(2) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(size.toDp())) + column(GridTrackSize.Fixed(0.dp)) // Zero width column + column(GridTrackSize.Fixed(size.toDp())) + row(GridTrackSize.Fixed(size.toDp())) + } + ) { + // Item 1: Col 1 + Box( + Modifier.gridItem(1, 1) + .size(size.toDp()) + .saveLayoutInfo(dummy, pos[0], latch) + ) + // Item 2: Col 3 (Skipping Col 2 which is 0 width) + Box( + Modifier.gridItem(1, 3) + .size(size.toDp()) + .saveLayoutInfo(dummy, pos[1], latch) + ) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Item 1 at 0 + assertEquals(Offset(0f, 0f), pos[0].value) + // Item 2 at 50 + 0 = 50 + assertEquals(Offset(size.toFloat(), 0f), pos[1].value) + } + + @Test + fun testGrid_zeroSizeChildren() { + val trackSize = 50 + val latch = CountDownLatch(1) + val gridSize = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(trackSize.toDp())) + row(GridTrackSize.Fixed(trackSize.toDp())) + }, + modifier = + Modifier.onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + // Place a zero-sized item. + // It should still occupy the logical cell (1,1), but draw nothing. + // The Grid should still size itself to the Fixed tracks (50x50). + Box(Modifier.gridItem(1, 1).size(0.dp)) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + assertEquals(IntSize(trackSize, trackSize), gridSize.value) + } + + @Test + fun testGrid_itemFillsCell_whenRequested() { + val trackSize = 100 + val latch = CountDownLatch(1) + val childSize = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(trackSize.toDp())) + row(GridTrackSize.Fixed(trackSize.toDp())) + } + ) { + // Item has no intrinsic size, but requests fillMaxSize(). + // It should fill the definition of the track (100x100). + Box(Modifier.gridItem(1, 1).fillMaxSize().saveLayoutInfo(childSize, Ref(), latch)) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + assertEquals(IntSize(trackSize, trackSize), childSize.value) + } + + @Test + fun testGrid_nestedGrid() = + with(density) { + val outerSize = 100 + val outerSizeDp = outerSize.toDp() + + // Inner grid will be placed in a 100x100 cell. + // It will have 2 columns of 50 each. + val latch = CountDownLatch(1) + val innerItemSize = Ref() + + show { + // Outer Grid + Grid( + config = { + column(GridTrackSize.Fixed(outerSizeDp)) + row(GridTrackSize.Fixed(outerSizeDp)) + } + ) { + // Inner Grid placed at (1,1) of Outer Grid + Grid( + modifier = Modifier.gridItem(1, 1).fillMaxSize(), + config = { + column(GridTrackSize.Flex(1.fr)) + column(GridTrackSize.Flex(1.fr)) + row(GridTrackSize.Flex(1.fr)) + }, + ) { + // Item inside Inner Grid (Col 1) + Box( + Modifier.gridItem(1, 1) + .fillMaxSize() + .saveLayoutInfo(innerItemSize, Ref(), latch) + ) + // Item inside Inner Grid (Col 2) just to fill space + Box(Modifier.gridItem(1, 2).fillMaxSize()) + } + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + assertEquals(IntSize(50, 100), innerItemSize.value) + } + + @Test(expected = IllegalArgumentException::class) + fun testGrid_invalidIndices_throws() { + show { + Grid( + config = { + column(GridTrackSize.Fixed(10.dp)) + row(GridTrackSize.Fixed(10.dp)) + } + ) { + Box(Modifier.gridItem(100000, 1)) // Out of bounds + } + } + } + + @Composable + private fun IntrinsicItem( + minWidth: Int, + minIntrinsicWidth: Int, + maxIntrinsicWidth: Int, + modifier: Modifier = Modifier, + ) { + Layout( + modifier, + measurePolicy = + object : MeasurePolicy { + override fun MeasureScope.measure( + measurables: List, + constraints: Constraints, + ): MeasureResult { + return layout( + constraints.minWidth.coerceAtLeast(minWidth), + constraints.minHeight, + ) {} + } + + override fun IntrinsicMeasureScope.minIntrinsicWidth( + measurables: List, + height: Int, + ) = minIntrinsicWidth + + override fun IntrinsicMeasureScope.maxIntrinsicWidth( + measurables: List, + height: Int, + ) = maxIntrinsicWidth + }, + ) + } +} diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt index daa683574a782..047a1062d6647 100644 --- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt +++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt @@ -17,22 +17,41 @@ package androidx.compose.foundation.layout import androidx.annotation.FloatRange +import androidx.collection.LongList +import androidx.collection.MutableObjectList +import androidx.collection.mutableLongListOf import androidx.compose.foundation.layout.GridScope.Companion.GridIndexUnspecified import androidx.compose.foundation.layout.GridScope.Companion.MaxGridIndex import androidx.compose.foundation.layout.internal.JvmDefaultWithCompatibility import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasurePolicy +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.layout.Placeable import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.node.ParentDataModifierNode import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.constrainHeight +import androidx.compose.ui.unit.constrainWidth +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach import kotlin.jvm.JvmInline +import kotlin.math.max +import kotlin.math.roundToInt /** * A 2D layout composable that arranges children into a grid of rows and columns. @@ -64,14 +83,20 @@ inline fun Grid( modifier: Modifier = Modifier, content: @Composable GridScope.() -> Unit, ) { + // Capture the latest config lambda in a State object. + // This ensures we always have access to the latest lambda without recreating the policy. + val currentConfig = rememberUpdatedState(config) + + // Create a stable MeasurePolicy instance. + // We use 'remember' without keys so the policy instance itself never changes. + // The policy reads 'currentConfig.value' inside measure(), triggering invalidation + // when the config changes. + val measurePolicy = remember { GridMeasurePolicy(currentConfig) } + Layout( content = { GridScopeInstance.content() }, modifier = modifier, - measurePolicy = - MeasurePolicy { _, constraints -> - // Implementation to be added in follow-up CL - layout(constraints.minWidth, constraints.minHeight) {} - }, + measurePolicy = measurePolicy, ) } @@ -552,3 +577,969 @@ private class GridItemNode( ) : Modifier.Node(), ParentDataModifierNode { override fun Density.modifyParentData(parentData: Any?) = this@GridItemNode } + +/** A stable MeasurePolicy that reads configuration from a State. */ +@PublishedApi +internal class GridMeasurePolicy( + private val configState: State Unit> +) : MeasurePolicy { + override fun MeasureScope.measure( + measurables: List, + constraints: Constraints, + ): MeasureResult { + // 1. Run Configuration DSL + val gridConfig = GridConfigurationScopeImpl(this).apply(configState.value) + + // 2. Resolve Grid Item Indices (Resolve explicit) (Auto placement added in followup cl) + // This calculates the concrete index (row, col) for every item and determines total grid + // size. + val resolvedGridItemsResult = + resolveGridItemIndices( + measurables = measurables, + columnSpecs = gridConfig.columnSpecs, + rowSpecs = gridConfig.rowSpecs, + flow = gridConfig.flow, + ) + + // 3. Resolve Track Sizes + val trackSizes = + calculateGridTrackSizes( + density = this, + gridItems = resolvedGridItemsResult.gridItems, + columnSpecs = gridConfig.columnSpecs, + rowSpecs = gridConfig.rowSpecs, + totalColCount = resolvedGridItemsResult.gridSize.width, + totalRowCount = resolvedGridItemsResult.gridSize.height, + columnGap = gridConfig.columnGap, + rowGap = gridConfig.rowGap, + constraints = constraints, + ) + + // 4. Measure Children + // Measures content constraints based on track sizes and mutates GridItem with result. + measureItems( + gridItems = resolvedGridItemsResult.gridItems, + trackSizes = trackSizes, + layoutDirection = layoutDirection, + ) + + // 5. Layout + // Coerce the final size within constraints. + // If content is larger, it will overflow (report Max). + // If content is smaller than Min, it will expand (report Min). + val layoutWidth = constraints.constrainWidth(trackSizes.totalWidth) + val layoutHeight = constraints.constrainHeight(trackSizes.totalHeight) + return layout(layoutWidth, layoutHeight) { + val columnOffsets = calculateTrackOffsets(trackSizes.columnWidths) + val rowOffsets = calculateTrackOffsets(trackSizes.rowHeights) + resolvedGridItemsResult.gridItems.forEach { gridItem -> + val placeable = gridItem.placeable + // Only place if measurement succeeded (guard against edge cases) + if (placeable != null) { + val x = columnOffsets[gridItem.column] + gridItem.offsetX + val y = rowOffsets[gridItem.row] + gridItem.offsetY + placeable.placeRelative(x, y) + } + } + } + } +} + +private class GridConfigurationScopeImpl(density: Density) : + GridConfigurationScope, Density by density { + val columnSpecs = mutableLongListOf() + val rowSpecs = mutableLongListOf() + var columnGap: Dp = 0.dp + var rowGap: Dp = 0.dp + + override var flow: GridFlow = GridFlow.Row + + override fun column(size: Dp) { + column(GridTrackSize.Fixed(size)) + } + + override fun column(weight: Fr) { + column(GridTrackSize.Flex(weight)) + } + + override fun column(percentage: Float) { + column(GridTrackSize.Percentage(percentage)) + } + + override fun column(size: GridTrackSize) { + columnSpecs.add(size.encodedValue) + } + + override fun row(size: Dp) { + row(GridTrackSize.Fixed(size)) + } + + override fun row(weight: Fr) { + row(GridTrackSize.Flex(weight)) + } + + override fun row(percentage: Float) { + row(GridTrackSize.Percentage(percentage)) + } + + override fun row(size: GridTrackSize) { + rowSpecs.add(size.encodedValue) + } + + // Gap implementation added in follow up cl + override fun gap(all: Dp) {} + + override fun gap(row: Dp, column: Dp) {} + + override fun columnGap(gap: Dp) {} + + override fun rowGap(gap: Dp) {} +} + +/** + * A mutable state object representing a single child in the Grid throughout the layout lifecycle. + * + * This object is created once during the [resolveGridItemIndices] phase (containing only placement + * info) and is reused and mutated during the [measureItems] phase to store the resulting + * [Placeable] and calculation offsets. This significantly reduces object allocation per layout + * pass. + */ +private class GridItem( + val measurable: Measurable, + var row: Int, + var column: Int, + var rowSpan: Int, + var columnSpan: Int, + val alignment: Alignment, + var placeable: Placeable? = null, + var offsetX: Int = 0, + var offsetY: Int = 0, +) + +/** + * The output of the [resolveGridItemIndices] algorithm. + * + * This container holds the complete layout plan required for subsequent measurement phases. It + * encapsulates both the individual item positions and the aggregate dimensions of the grid. + * + * The [gridSize] is critical because it reveals the extent of the "Implicit Grid" — tracks that + * were not explicitly defined by the user but were created automatically to accommodate auto-placed + * items or items with out-of-bounds indices. + * + * @property gridItems The list of all items with their resolved (row, column) coordinates. + * @property gridSize The total number of rows and columns required to house all items. (width = + * total columns, height = total rows). + */ +private class ResolvedGridItemIndicesResult( + val gridItems: MutableObjectList, + val gridSize: IntSize, +) + +/** + * The "Master Blueprint" holding the calculated pixel dimensions for the entire grid. + * + * This class acts as a lookup table during the measurement phase. Instead of recalculating sizes + * for every item, we compute the track sizes once and pass this object around. + * + * @property columnWidths Array containing the exact width in pixels for each column index. + * @property rowHeights Array containing the exact height in pixels for each row index. + * @property totalWidth The sum of all column widths plus gaps. + * @property totalHeight The sum of all row heights plus gaps. + * @property columnGapPx The spacing between columns. + * @property rowGapPx The spacing between rows. + */ +private class GridTrackSizes( + val columnWidths: IntArray, + val rowHeights: IntArray, + val totalWidth: Int, + val totalHeight: Int, + val columnGapPx: Int, + val rowGapPx: Int, +) + +/** + * Executes the "Sparse Packing" auto-placement algorithm to resolve every item's position. + * + * This function is the "Engine" of the auto-placement logic. It transforms a list of raw + * measurables (with potentially unspecified `row`/`column` values) into a concrete plan where every + * item has a specific (row, column) coordinate. + * + * **Algorithm Overview:** + * 1. **Explicit Placement:** Items with both `row` and `column` manually specified are placed + * first. They anchor the grid and do not move. + * + * @param measurables The raw list of children to place. + * @param columnSpecs The explicit column definitions (used to determine wrapping points). + * @param rowSpecs The explicit row definitions (used to determine wrapping points). + * @param flow The direction ([GridFlow.Row] or [GridFlow.Column]) to fill the grid. + * @return A [ResolvedGridItemIndicesResult] containing the final positions and the *total* grid + * dimensions (Explicit + Implicit). + * + * TODO: handle auto placement + */ +private fun resolveGridItemIndices( + measurables: List, + columnSpecs: LongList, + rowSpecs: LongList, + flow: GridFlow, +): ResolvedGridItemIndicesResult { + val gridItems = MutableObjectList(measurables.size) + val explicitColCount = columnSpecs.size + val explicitRowCount = rowSpecs.size + + measurables.fastForEach { measurable -> + val data = measurable.parentData as? GridItemNode + val rowSpan = data?.rowSpan ?: 1 + val colSpan = data?.columnSpan ?: 1 + + // Convert 1-based user indices to 0-based internal indices. + // Returns null if the user index was unspecified (Auto). + val requestedRow = + resolveToZeroBasedIndex(data?.row ?: GridIndexUnspecified, explicitRowCount) + val requestedCol = + resolveToZeroBasedIndex(data?.column ?: GridIndexUnspecified, explicitColCount) + + var finalRow = -1 + var finalCol = -1 + + // 1. Fully Explicit (Row & Column fixed) + // We simply place it there. Overlaps are allowed for explicit placement. + if (requestedRow != -1 && requestedCol != -1) { + finalRow = requestedRow + finalCol = requestedCol + } + // TODO Handle Fixed Row, Or Fixed Column or Fully Auto added in followup cl + + // If auto-placement failed to find a spot (e.g. MaxGridIndex reached), + // we default to 0,0 to avoid crashing, though visual overlap will occur. + val placementRow = max(0, finalRow) + val placementCol = max(0, finalCol) + + // Populate the mutable GridItem + gridItems.add( + GridItem( + measurable = measurable, + row = placementRow, + column = placementCol, + rowSpan = rowSpan, + columnSpan = colSpan, + alignment = Alignment.TopStart, // TODO Handle alignment in followup cl + ) + ) + } + + return ResolvedGridItemIndicesResult(gridItems, IntSize(explicitColCount, explicitRowCount)) +} + +/** + * Resolves a 1-based user index (positive or negative) to a 0-based concrete index. + * + * @param index The user-provided index (e.g., 1, -1, or [GridIndexUnspecified]). + * @param maxCount The number of explicit tracks defined (used for negative index resolution). + * @return The 0-based index, or -1 if the index was unspecified or invalid (e.g. negative index out + * of bounds). + */ +private fun resolveToZeroBasedIndex(index: Int, maxCount: Int): Int { + if (index == GridIndexUnspecified) return -1 + + // Positive Index (e.g., 5): Maps to 4. + // Always valid (allows creating implicit tracks if > maxCount). + if (index > 0) return index - 1 + + // Negative Index (e.g., -1): Maps to maxCount - 1. + // Must check if it points to a valid explicit track [0..maxCount-1]. + // If it points before 0 (e.g. -5 in a 2-row grid), it is invalid. + val resolved = maxCount + index + return if (resolved >= 0) resolved else -1 +} + +/** + * Resolves the abstract [GridTrackSize] specifications for all rows and columns into concrete pixel + * dimensions. This function is the core of the size calculation logic for the [Grid]. + * + * **Calculation Order:** The calculation is performed in two main phases: + * 1. **Column Widths:** Column widths are calculated first. This is crucial because the height of + * many UI elements (like text) depends on the available width. + * 2. **Row Heights:** Row heights are calculated second, utilizing the resolved column widths to + * accurately measure items, especially those with content that wraps. + * + * **Track Type Resolution:** Within each phase, different [GridTrackSize] types are resolved as + * follows: + * - [GridTrackSize.Fixed]: Converted directly to pixels using the [density]. + * - [GridTrackSize.Percentage]: Calculated based on the available space for tracks (after + * subtracting gaps). Falls back to content-based size (MaxContent) if the available space on that + * axis is infinite (e.g., in a scrollable container). + * - [GridTrackSize.MinContent], [GridTrackSize.MaxContent], [GridTrackSize.Auto]: Determined by + * measuring the intrinsic sizes of the items within the track. `Auto` typically behaves like + * `MaxContent`. + * - [GridTrackSize.Flex]: Initially sized to their minimum content size. After all other types and + * spanning items are accounted for, any remaining space is distributed proportionally among flex + * tracks. + * + * **Implicit Tracks:** Tracks not explicitly defined in [columnSpecs] or [rowSpecs] (i.e., indices + * beyond the spec list sizes) are treated as `GridTrackSize.Auto`. + * + * **Spanning Items:** The function accounts for items spanning multiple tracks, potentially + * increasing the sizes of growable tracks ([Auto], [MinContent], [MaxContent], [Flex]) to + * accommodate them. + * + * @param density The current screen density, used for converting Dp to pixels. + * @param gridItems The list of all grid items, including their placement and spans. + * @param columnSpecs The explicit configurations for columns. + * @param rowSpecs The explicit configurations for rows. + * @param totalColCount The total number of columns in the grid (explicit + implicit). + * @param totalRowCount The total number of rows in the grid (explicit + implicit). + * @param constraints The layout constraints from the parent composable. + * @param columnGap The spacing in Dp between columns. + * @param rowGap The spacing in Dp between rows. + * @return A [GridTrackSizes] object containing the calculated pixel sizes for each column and row, + * the total grid dimensions, and the gap sizes in pixels. + */ +private fun calculateGridTrackSizes( + density: Density, + gridItems: MutableObjectList, + columnSpecs: LongList, + rowSpecs: LongList, + totalColCount: Int, // Total (Implicit + Explicit) + totalRowCount: Int, // Total (Implicit + Explicit) + constraints: Constraints, + columnGap: Dp, + rowGap: Dp, +): GridTrackSizes { + val colGapPx = with(density) { columnGap.roundToPx() } + val rowGapPx = with(density) { rowGap.roundToPx() } + + // Group items by track index to avoid O(Tracks * Items) loop + // Array of lists, where index corresponds to the column index + val itemsByColumn = arrayOfNulls>(totalColCount) + // Array of lists, where index corresponds to the row index + val itemsByRow = arrayOfNulls>(totalRowCount) + + gridItems.forEach { item -> + // Populate Column Lookup + if (item.column < totalColCount) { + val list = + itemsByColumn[item.column] + ?: MutableObjectList().also { itemsByColumn[item.column] = it } + list.add(item) + } + // Populate Row Lookup + if (item.row < totalRowCount) { + val list = + itemsByRow[item.row] + ?: MutableObjectList().also { itemsByRow[item.row] = it } + list.add(item) + } + } + + // --- Phase 1: Calculate Column Widths --- + // Use totalColCount for array size + val columnWidths = IntArray(totalColCount) + // If constraints are infinite (e.g. horizontal scroll), we pass Infinity. + // This triggers the fallback logic in calculateAxisSize (Percentage -> Auto). + val availableWidth = + if (constraints.hasFixedWidth) constraints.maxWidth else Constraints.Infinity + + val totalTrackWidth = + calculateColumnWidths( + density = density, + explicitSpecs = columnSpecs, + totalCount = totalColCount, + availableSpace = availableWidth, + outSizes = columnWidths, + itemsByColumn = itemsByColumn, + constraints = constraints, + gridItems = gridItems, + ) + + // --- Phase 2: Calculate Row Heights --- + val rowHeights = IntArray(totalRowCount) + val availableHeight = + if (constraints.hasFixedHeight) constraints.maxHeight else Constraints.Infinity + + val totalTrackHeight = + calculateRowHeights( + density = density, + explicitSpecs = rowSpecs, + totalCount = totalRowCount, + availableSpace = availableHeight, + outSizes = rowHeights, + itemsByRow = itemsByRow, + constraints = constraints, + columnWidths = columnWidths, + gridItems = gridItems, + ) + + return GridTrackSizes( + columnWidths = columnWidths, + rowHeights = rowHeights, + columnGapPx = colGapPx, + rowGapPx = rowGapPx, + totalWidth = totalTrackWidth, + totalHeight = totalTrackHeight, + ) +} + +/** + * Calculates the specific pixel width of every column in the grid. + * + * This function implements the horizontal axis sizing logic. It resolves column widths based on + * explicit configuration, available space, and content intrinsic sizes. + * + * **Algorithm Overview:** + * 1. **Pass 1 (Base Sizes):** Calculates the initial width of each column based on its + * [GridTrackSize]. + * * **Implicit Tracks:** Indices beyond `explicitSpecs` default to [GridTrackSize.Auto]. + * * **Fixed:** Resolves directly to pixels. + * * **Percentage:** Resolves against total available width. Falls back to `Auto` (MaxContent) if + * width is infinite (e.g., inside a container made horizontally scrollable with the + * `horizontalScroll` modifier). + * * **Flex:** Starts at `min-content` size to prevent collapse if content exists. + * * **Auto/Content-based:** Measured using the intrinsic width of items in that column. + * 2. **Pass 1.5 (Spanning Items):** Increases column widths if an item spanning multiple columns + * requires more width than the sum of those columns. + * 3. **Pass 2 (Flex Distribution):** Distributes any remaining horizontal space among + * [GridTrackSize.Flex] columns according to their weight. + * + * @param density Used for Dp-to-Px conversion. + * @param explicitSpecs The user-defined column configurations. + * @param totalCount The total number of columns (explicit + implicit). + * @param availableSpace The maximum width available (or [Constraints.Infinity]). + * @param outSizes Output array where calculated widths are stored. **Mutated in-place**. + * @param itemsByColumn Optimization lookup: List of items starting in each column index. + * @param constraints Parent constraints (used for fallback behavior and cross-axis limits). + * @param gridItems All items in the grid (used for spanning logic). + * @return The total used width in pixels (sum of all column widths). + */ +private fun calculateColumnWidths( + density: Density, + explicitSpecs: LongList, + totalCount: Int, + availableSpace: Int, + outSizes: IntArray, + itemsByColumn: Array?>, + constraints: Constraints, + gridItems: MutableObjectList, +): Int { + if (totalCount == 0) return 0 + + var totalFlex = 0f + + // Height constraint used when measuring intrinsic width. + // Usually Infinity (standard intrinsic measurement), unless parent enforces strict height. + val crossAxisAvailable = + if (constraints.hasBoundedHeight) constraints.maxHeight else Constraints.Infinity + + // --- Pass 1: Base Sizes (Single-Span Items) --- + // Iterate through every column index (both explicit and implicit). + for (index in 0 until totalCount) { + // If index exceeds explicit specs, treat it as an Implicit Auto track. + val specRaw = + if (index < explicitSpecs.size) explicitSpecs[index] + else GridTrackSize.Auto.encodedValue + val spec = GridTrackSize(specRaw) + + val size = + when (spec.type) { + GridTrackSize.TypeFixed -> with(density) { spec.value.dp.roundToPx() } + + GridTrackSize.TypePercentage -> { + if (availableSpace != Constraints.Infinity) { + (spec.value * availableSpace).roundToInt() + } else { + // If the Grid is in a horizontally scrolling container + // (infinite width), we cannot calculate a percentage of "Infinity". + // We default to 'Auto' (MaxIntrinsic) so the content remains visible. + calculateMaxIntrinsicWidth(itemsByColumn[index], crossAxisAvailable) + } + } + + GridTrackSize.TypeFlex -> { + totalFlex += spec.value + // Flex tracks start at their 'min-content' size. + // This implements `minmax(min-content, fr)`. + // It ensures that even if there is no remaining space to distribute, + // the column is at least wide enough to show its content. + calculateMinIntrinsicWidth(itemsByColumn[index], crossAxisAvailable) + } + + // Auto, MinContent, MaxContent (Implicit tracks fall here) + // Measure the max intrinsic width of all items in this column. + else -> calculateMaxIntrinsicWidth(itemsByColumn[index], crossAxisAvailable) + } + outSizes[index] = size + } + + // --- Pass 1.5: Spanning Items --- + // If an item spans 2 columns, and those 2 columns (base sizes) sum to 100px, but the item + // is 150px wide, we must grow the columns by 50px. + distributeSpanningSpace( + explicitSpecs = explicitSpecs, + sizes = outSizes, + gridItems = gridItems, + isRowAxis = false, + constraints = constraints, + crossAxisSizes = null, // Not needed for column width calculation + ) + + var usedSpace = 0 + for (size in outSizes) { + usedSpace += size + } + + // --- Pass 2: Flex Distribution --- + // If we have finite width and unused space, distribute it to Flex columns. + val remainingSpace = + if (availableSpace == Constraints.Infinity) 0 else max(0, availableSpace - usedSpace) + + var totalAddedFromFlex = 0 + if (totalFlex > 0 && remainingSpace > 0) { + var distributed = 0 + var accumulatedFlex = 0f + + for (index in 0 until totalCount) { + val specRaw = + if (index < explicitSpecs.size) explicitSpecs[index] + else GridTrackSize.Auto.encodedValue + val spec = GridTrackSize(specRaw) + + if (spec.type == GridTrackSize.TypeFlex) { + accumulatedFlex += spec.value + // Distribute space proportionally based on weight. + // Uses an accumulation algorithm to avoid rounding errors summing to > + // remainingSpace. + val targetSpace = (accumulatedFlex / totalFlex * remainingSpace).roundToInt() + val share = max(0, targetSpace - distributed) + + outSizes[index] += share + distributed += share + totalAddedFromFlex = distributed + } + } + } + + return usedSpace + totalAddedFromFlex +} + +/** + * Calculates the specific pixel height of every row in the grid. + * + * This function implements the vertical axis sizing logic. Unlike columns (which are usually fixed + * or determined by parent width), row heights often depend on the *width* of the content within + * them. Therefore, this function **must** be called after [calculateColumnWidths]. + * + * **Algorithm Overview:** + * 1. **Pass 1 (Base Sizes):** Calculates the initial height of each row based on its + * [GridTrackSize]. + * * **Implicit Tracks:** Indices beyond `explicitSpecs` default to [GridTrackSize.Auto]. + * * **Auto/Content-based:** Measured using the pre-calculated `columnWidths`. This ensures text + * wraps correctly within its specific cell width. + * * **Percentage:** Resolves against total height. Falls back to `Auto` if height is infinite + * (e.g., inside a ScrollView). + * * **Flex:** Starts at `min-content` size to prevent collapse if content exists. + * 2. **Pass 1.5 (Spanning Items):** Increases row heights if an item spanning multiple rows is + * taller than the sum of those rows. + * 3. **Pass 2 (Flex Distribution):** Distributes any remaining vertical space among + * [GridTrackSize.Flex] rows according to their weight. + * + * @param density Used for Dp-to-Px conversion. + * @param explicitSpecs The user-defined row configurations. + * @param totalCount The total number of rows (explicit + implicit). + * @param availableSpace The maximum height available (or [Constraints.Infinity]). + * @param outSizes Output array where calculated heights are stored. **Mutated in-place**. + * @param itemsByRow Optimization lookup: List of items starting in each row index. + * @param constraints Parent constraints (used for max height limits). + * @param columnWidths The resolved widths of columns. **Critical** for measuring text height. + * @param gridItems All items in the grid (used for spanning logic). + * @return The total used height in pixels (sum of all row heights). + */ +private fun calculateRowHeights( + density: Density, + explicitSpecs: LongList, + totalCount: Int, + availableSpace: Int, + outSizes: IntArray, + itemsByRow: Array?>, + constraints: Constraints, + columnWidths: IntArray, + gridItems: MutableObjectList, +): Int { + if (totalCount == 0) return 0 + + var totalFlex = 0f + + // --- Pass 1: Base Sizes (Single-Span Items) --- + // We iterate through every row index (both explicit and implicit). + for (index in 0 until totalCount) { + // If index exceeds explicit specs, treat it as an Implicit Auto track. + val specRaw = + if (index < explicitSpecs.size) explicitSpecs[index] + else GridTrackSize.Auto.encodedValue + val spec = GridTrackSize(specRaw) + + val size = + when (spec.type) { + GridTrackSize.TypeFixed -> with(density) { spec.value.dp.roundToPx() } + + GridTrackSize.TypePercentage -> { + if (availableSpace != Constraints.Infinity) { + (spec.value * availableSpace).roundToInt() + } else { + // If the Grid is in a vertically scrolling container + // (infinite height), we cannot calculate a percentage of "Infinity". + // We default to 'Auto' (MaxIntrinsic) so the content remains visible. + calculateMaxIntrinsicHeight( + items = itemsByRow[index], + columnWidths = columnWidths, + fallbackWidth = constraints.maxWidth, + ) + } + } + + GridTrackSize.TypeFlex -> { + totalFlex += spec.value + // Flex tracks start at their 'min-content' size. + // This implements `minmax(min-content, fr)`. + // It ensures that even if there is no remaining space to distribute, + // the row is at least tall enough to show its content. + calculateMinIntrinsicHeight( + items = itemsByRow[index], + columnWidths = columnWidths, + fallbackWidth = constraints.maxWidth, + ) + } + + // Auto, MinContent, MaxContent (Implicit tracks fall here) + else -> + calculateMaxIntrinsicHeight( + items = itemsByRow[index], + columnWidths = columnWidths, + fallbackWidth = constraints.maxWidth, + ) + } + outSizes[index] = size + } + + // --- Pass 1.5: Spanning Items --- + // If an item spans 2 rows, and those 2 rows (base sizes) sum to 100px, but the item + // is 150px tall, we must grow the rows by 50px. + distributeSpanningSpace( + explicitSpecs = explicitSpecs, + sizes = outSizes, + gridItems = gridItems, + isRowAxis = true, + constraints = constraints, + crossAxisSizes = columnWidths, + ) + + var usedSpace = 0 + for (size in outSizes) { + usedSpace += size + } + + // --- Pass 2: Flex Distribution --- + // + // If we have finite height and unused space, distribute it to Flex rows. + val remainingSpace = + if (availableSpace == Constraints.Infinity) 0 else max(0, availableSpace - usedSpace) + + var totalAddedFromFlex = 0 + if (totalFlex > 0 && remainingSpace > 0) { + var distributed = 0 + var accumulatedFlex = 0f + + for (index in 0 until totalCount) { + val specRaw = + if (index < explicitSpecs.size) explicitSpecs[index] + else GridTrackSize.Auto.encodedValue + val spec = GridTrackSize(specRaw) + + if (spec.type == GridTrackSize.TypeFlex) { + accumulatedFlex += spec.value + // Distribute space proportionally based on weight. + // Uses an accumulation algorithm to avoid rounding errors summing to > + // remainingSpace. + val targetSpace = (accumulatedFlex / totalFlex * remainingSpace).roundToInt() + val share = max(0, targetSpace - distributed) + + outSizes[index] += share + distributed += share + totalAddedFromFlex = distributed + } + } + } + + return usedSpace + totalAddedFromFlex +} + +private fun calculateMaxIntrinsicWidth( + items: MutableObjectList?, + heightConstraint: Int, +): Int { + if (items == null) return 0 + var maxSize = 0 + items.forEach { item -> + if (item.columnSpan == 1) { + val size = item.measurable.maxIntrinsicWidth(heightConstraint) + if (size > maxSize) maxSize = size + } + } + return maxSize +} + +private fun calculateMinIntrinsicWidth( + items: MutableObjectList?, + heightConstraint: Int, +): Int { + if (items == null) return 0 + var maxSize = 0 + items.forEach { item -> + if (item.columnSpan == 1) { + val size = item.measurable.minIntrinsicWidth(heightConstraint) + if (size > maxSize) maxSize = size + } + } + return maxSize +} + +private fun calculateMaxIntrinsicHeight( + items: MutableObjectList?, + columnWidths: IntArray, + fallbackWidth: Int, +): Int { + if (items == null) return 0 + var maxSize = 0 + items.forEach { item -> + if (item.rowSpan == 1) { + val colIndex = item.column + val width = if (colIndex < columnWidths.size) columnWidths[colIndex] else fallbackWidth + val size = item.measurable.maxIntrinsicHeight(width) + if (size > maxSize) maxSize = size + } + } + return maxSize +} + +private fun calculateMinIntrinsicHeight( + items: MutableObjectList?, + columnWidths: IntArray, + fallbackWidth: Int, +): Int { + if (items == null) return 0 + var maxSize = 0 + items.forEach { item -> + if (item.rowSpan == 1) { + val colIndex = item.column + val width = if (colIndex < columnWidths.size) columnWidths[colIndex] else fallbackWidth + val size = item.measurable.minIntrinsicHeight(width) + if (size > maxSize) maxSize = size + } + } + return maxSize +} + +/** + * Increases the size of "growable" tracks (Auto, Flex, MinContent, MaxContent) to accommodate items + * that span across multiple tracks. + * + * This represents **Pass 1.5** of the grid sizing algorithm. It runs after base track sizes + * (Pass 1) are calculated but before flexible space (Pass 2) is distributed. + * + * **The Problem:** An item spanning 2 columns might have a minimum intrinsic width of 200px. If the + * base size of those 2 columns (plus the gap) only equals 150px, the item will be clipped or + * overlap. + * + * **The Solution (Deficit Distribution):** + * 1. Calculate the **Deficit**: `RequiredSize - (SumOfTracks + SumOfGaps)`. + * 2. Distribute this deficit evenly among the tracks involved in the span, *excluding* rigid tracks + * ([GridTrackSize.Fixed] and [GridTrackSize.Percentage]). + * + * @param explicitSpecs The user-defined track specifications. Used to determine if a track is rigid + * (Fixed/Percentage) or growable (Intrinsic). + * @param sizes The current calculated pixel sizes of the tracks. + * @param gridItems The list of all items to check for spanning requirements. + * @param isRowAxis `true` if calculating Row Heights, `false` if calculating Column Widths. + * @param constraints The parent layout constraints. + * @param crossAxisSizes The calculated sizes of the *opposite* axis (e.g., Column Widths when + * calculating Row Heights). This is crucial for correctly measuring the intrinsic height of items + * that wrap text based on specific column widths. + */ +private fun distributeSpanningSpace( + explicitSpecs: LongList, + sizes: IntArray, + gridItems: MutableObjectList, + isRowAxis: Boolean, + constraints: Constraints, + crossAxisSizes: IntArray?, +) { + gridItems.forEach { item -> + val trackIndex = if (isRowAxis) item.row else item.column + val span = if (isRowAxis) item.rowSpan else item.columnSpan + + // Single-span items were already handled during Base Size calculation (Pass 1). + if (span <= 1) return@forEach + + val endIndex = (trackIndex + span).coerceAtMost(sizes.size) + + // --- Step 1: Analyze current space & identifying growable tracks --- + // We sum the current size of all tracks this item spans to see if they are already big + // enough. + var currentSpannedSize = 0 + var tracksToGrowCount = 0 + + for (i in trackIndex until endIndex) { + currentSpannedSize += sizes[i] + + // Implicit tracks (indices >= specs.size) default to Auto. + val specRaw = + if (i < explicitSpecs.size) explicitSpecs[i] else GridTrackSize.Auto.encodedValue + val spec = GridTrackSize(specRaw) + + // Fixed and Percentage tracks are considered "Rigid". They respect the user's explicit + // definition and do not expand to fit content from spanning items. + // Only Intrinsic tracks (Auto, Flex, Min/MaxContent) absorb the deficit. + if (spec.type != GridTrackSize.TypeFixed && spec.type != GridTrackSize.TypePercentage) { + tracksToGrowCount++ + } + } + + // --- Step 2: Calculate the Item's Required Size (Intrinsic Measurement) --- + // This differs based on the axis. + val requiredSize = + if (isRowAxis) { + // Case: Calculating Row Heights. + // To get the correct intrinsic height (e.g., for wrapping text), we need to know + // the exact width the item occupies. This is the sum of the columns it spans. + var itemWidth = 0 + if (crossAxisSizes != null) { + val colStart = item.column + val colEnd = (colStart + item.columnSpan).coerceAtMost(crossAxisSizes.size) + for (i in colStart until colEnd) { + itemWidth += crossAxisSizes[i] + } + // TODO: Add gap handling + } else { + // If we don't know column widths, constrain only by parent max. + itemWidth = constraints.maxWidth + } + item.measurable.maxIntrinsicHeight(itemWidth) + } else { + // Case: Calculating Column Widths. + // Intrinsic width is typically calculated against infinite height (maxContent), + // or the parent's bounded height if specified. + val heightConstraint = + if (constraints.hasBoundedHeight) constraints.maxHeight + else Constraints.Infinity + item.measurable.maxIntrinsicWidth(heightConstraint) + } + + // --- Step 3: Distribute Deficit --- + val deficit = requiredSize - currentSpannedSize + + // If the item needs more space than currently available, and we have eligible tracks to + // grow, we distribute the missing pixels evenly. + if (deficit > 0 && tracksToGrowCount > 0) { + val share = deficit / tracksToGrowCount + var remainder = deficit % tracksToGrowCount + + for (i in trackIndex until endIndex) { + val specRaw = + if (i < explicitSpecs.size) explicitSpecs[i] + else GridTrackSize.Auto.encodedValue + val spec = GridTrackSize(specRaw) + + // Only add space to the "growable" tracks identified in Step 1. + if ( + spec.type != GridTrackSize.TypeFixed && + spec.type != GridTrackSize.TypePercentage + ) { + // Add the base share + 1 pixel if we still have remainder to distribute. + // This ensures (share * count) + remainder == total deficit. + val add = share + if (remainder > 0) 1 else 0 + sizes[i] += add + if (remainder > 0) remainder-- + } + } + } + } +} + +/** + * Measures the content of every grid item based on its resolved position and span. + * + * This function converts abstract grid coordinates (row/column indices) into concrete pixel + * constraints. It determines the exact width and height of the cell(s) an item spans and measures + * the child content against those bounds. + * + * This method calculates the span size in O(1) time using the pre-computed offset arrays: `Size = + * (End_Offset + End_Size) - Start_Offset` + * + * This function mutates the provided [gridItems] list, updating each item with its measured + * [Placeable] and calculated (x, y) offsets. + * + * @param gridItems The list of all grid items. + * @param trackSizes The calculated pixel sizes for every row and column track. + * @param layoutDirection The current layout direction. + */ +private fun measureItems( + gridItems: MutableObjectList, + trackSizes: GridTrackSizes, + layoutDirection: LayoutDirection, +) { + val rowCount = trackSizes.rowHeights.size + val colCount = trackSizes.columnWidths.size + + gridItems.forEach { item -> + val row = item.row + val col = item.column + + if (row < rowCount && col < colCount) { + var width = 0 + val colLimit = (col + item.columnSpan).coerceAtMost(colCount) + for (i in col until colLimit) { + width += trackSizes.columnWidths[i] + } + // TODO: Add gaps for spanned columns + + var height = 0 + val rowLimit = (row + item.rowSpan).coerceAtMost(rowCount) + for (i in row until rowLimit) { + height += trackSizes.rowHeights[i] + } + // TODO: Add gaps for spanned rows + + // Use loose constraints to allow alignment to work. + // If strict fixed constraints are used, child size == cell size, so alignment is + // ignored. + val constraints = Constraints(maxWidth = width, maxHeight = height) + val placeable = item.measurable.measure(constraints) + + item.placeable = placeable + } + } +} + +/** + * Computes the cumulative starting position (offset) for each track. + * + * This function converts a list of track sizes (e.g., column widths or row heights) into absolute + * coordinates by accumulating the size of previous tracks. + * + * Example logic: + * - Offset[0] = 0 + * - Offset[1] = Size[0] + * - Offset[2] = Size[0] + Size[1] + * + * @param sizes An array containing the size of each individual track. + * @return An [IntArray] of the same length as [sizes], where index `i` contains the starting + * coordinate of that track. + */ +private fun calculateTrackOffsets(sizes: IntArray): IntArray { + val offsets = IntArray(sizes.size) + var current = 0 + for (i in sizes.indices) { + offsets[i] = current + current += sizes[i] + } + return offsets +} From e0455b43508c936ef3433c015437e6a762e4bf03 Mon Sep 17 00:00:00 2001 From: Prashant Date: Wed, 10 Dec 2025 05:10:42 +0000 Subject: [PATCH 05/19] Add support for gaps in Grid layout This commit introduces gap support to the `Grid` composable, allowing developers to define spacing between rows and columns. Key changes: - Configuration: `GridConfigurationScope` now supports `gap`, `rowGap`, and `columnGap` methods to configure spacing. - Track Sizing: Grid track size calculation logic has been updated to subtract total gap space from the available container size before allocating space for fractional and flexible tracks. - Measurement: Measurement phase now accounts for gaps when calculating the size of items that span multiple tracks (e.g., width = sum of columns + gaps between them). - Layout: Offset now includes gap pixels when determining the start position of each track. Also updated integration demos and added unit tests to verify gap behavior and alignment with spanning items. Bug: 462550392 Test: GridTest Change-Id: I38eb4f640aa4d8d29ee0858ca246f4e23ed26767 --- .../foundation/layout/demos/GridDemo.kt | 24 +++ .../compose/foundation/layout/GridTest.kt | 160 ++++++++++++++++++ .../compose/foundation/layout/Grid.kt | 111 +++++++++--- 3 files changed, 272 insertions(+), 23 deletions(-) diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt index 9dbc5059e743c..b778d93c0cb4c 100644 --- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt +++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt @@ -58,6 +58,8 @@ fun GridDemo() { Spacer(Modifier.height(32.dp)) NegativeIndicesDemo() Spacer(Modifier.height(32.dp)) + GapsDemo() + Spacer(Modifier.height(32.dp)) } } @@ -128,6 +130,28 @@ fun NegativeIndicesDemo() { } } +@Composable +private fun GapsDemo() { + DemoHeader("Gaps Demo") + Grid( + config = { + column(GridTrackSize.Fixed(100.dp)) + column(GridTrackSize.Flex(1.fr)) + column(GridTrackSize.Fixed(200.dp)) + row(GridTrackSize.Fixed(60.dp)) + row(GridTrackSize.Fixed(100.dp)) + gap(row = 12.dp, column = 6.dp) + }, + modifier = Modifier.demoContainer(borderColor = Color.Cyan), + ) { + repeat(6) { + val row = (it / 3) + 1 + val col = (it % 3) + 1 + GridDemoItem(text = "Item ${it + 1}", measureSize = false, row = row, column = col) + } + } +} + @Composable private fun DemoHeader(text: String) = Text( diff --git a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt index 3764edf0c7e31..5c6e62c958b7c 100644 --- a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt +++ b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt @@ -1066,6 +1066,166 @@ class GridTest : LayoutTest() { assertEquals(IntSize(trackSize, trackSize), childSize.value) } + @Test + fun testGrid_gaps() = + with(density) { + val size = 50 + val gap = 10 + val gapDp = gap.toDp() + val sizeDp = size.toDp() + + val positionedLatch = CountDownLatch(2) + val childPosition = Array(2) { Ref() } + // Use explicit dummy refs instead of passing null to avoid overload ambiguity + val dummySize = Array(2) { Ref() } + + show { + Grid( + config = { + column(GridTrackSize.Fixed(sizeDp)) + column(GridTrackSize.Fixed(sizeDp)) + row(GridTrackSize.Fixed(sizeDp)) + gap(gapDp) + } + ) { + // Item 1: (0, 0) + Box( + Modifier.gridItem(1, 1) + .fillMaxSize() + .saveLayoutInfo(dummySize[0], childPosition[0], positionedLatch) + ) + // Item 2: (50 + 10, 0) = (60, 0) + Box( + Modifier.gridItem(1, 2) + .fillMaxSize() + .saveLayoutInfo(dummySize[1], childPosition[1], positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), childPosition[0].value) + assertEquals(Offset((size + gap).toFloat(), 0f), childPosition[1].value) + } + + @Test + fun testGrid_flexWithGaps() = + with(density) { + val size = 50 + val gap = 10 + // Available space for tracks: 50 - 10 = 40. + // 1.fr + 1.fr = 2 parts. 40 / 2 = 20 per track. + val expectedColWidth = (size - gap) / 2 + + val positionedLatch = CountDownLatch(2) + val childSize = Array(2) { Ref() } + val childPosition = Array(2) { Ref() } + + show { + Grid( + config = { + column(GridTrackSize.Flex(1.fr)) + column(GridTrackSize.Flex(1.fr)) + row(GridTrackSize.Fixed(size.toDp())) + gap(gap.toDp()) + }, + modifier = Modifier.requiredSize(size.toDp()), + ) { + Box( + Modifier.gridItem(1, 1) + .fillMaxSize() + .saveLayoutInfo(childSize[0], childPosition[0], positionedLatch) + ) + Box( + Modifier.gridItem(1, 2) + .fillMaxSize() + .saveLayoutInfo(childSize[1], childPosition[1], positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + assertEquals(IntSize(expectedColWidth, size), childSize[0].value) + assertEquals(IntSize(expectedColWidth, size), childSize[1].value) + assertEquals(Offset(0f, 0f), childPosition[0].value) + assertEquals(Offset((expectedColWidth + gap).toFloat(), 0f), childPosition[1].value) + } + + @Test + fun testGrid_spanningWithGaps() { + val size = 50 + val gap = 10 + // Spanning 2 columns: size + gap + size = 50 + 10 + 50 = 110 + val expectedWidth = size * 2 + gap + + val positionedLatch = CountDownLatch(1) + val childSize = Ref() + val dummyPos = Ref() + + show { + Grid( + config = { + repeat(3) { column(GridTrackSize.Fixed(size.toDp())) } + row(GridTrackSize.Fixed(size.toDp())) + gap(gap.toDp()) + } + ) { + Box( + Modifier.gridItem(row = 1, column = 1, columnSpan = 2) + .fillMaxSize() + .saveLayoutInfo(childSize, dummyPos, positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + assertEquals(IntSize(expectedWidth, size), childSize.value) + } + + @Test + fun testGrid_gapPrecedence_specificOverridesGeneric() = + with(density) { + // Scenario: + // gap(10) sets both. + // rowGap(20) overrides row gap. + // columnGap(5) overrides column gap. + // Result: RowGap = 20, ColumnGap = 5. + + val size = 50 + val baseGap = 10 + val rowGapOverride = 20 + val colGapOverride = 5 + + val sizeDp = size.toDp() + val latch = CountDownLatch(3) + val pos = Array(3) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + repeat(2) { column(GridTrackSize.Fixed(sizeDp)) } + repeat(2) { row(GridTrackSize.Fixed(sizeDp)) } + + gap(baseGap.toDp()) // Sets both to 10 + rowGap(rowGapOverride.toDp()) // Overrides row to 20 + columnGap(colGapOverride.toDp()) // Overrides col to 5 + } + ) { + // (0,0) + Box(Modifier.gridItem(1, 1).size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) + // (0,1) -> X should be Size + ColGap(5) + Box(Modifier.gridItem(1, 2).size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) + // (1,0) -> Y should be Size + RowGap(20) + Box(Modifier.gridItem(2, 1).size(sizeDp).saveLayoutInfo(dummy, pos[2], latch)) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset((size + colGapOverride).toFloat(), 0f), pos[1].value) + assertEquals(Offset(0f, (size + rowGapOverride).toFloat()), pos[2].value) + } + @Test fun testGrid_nestedGrid() = with(density) { diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt index 047a1062d6647..0ca1d8f9ee8db 100644 --- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt +++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt @@ -630,8 +630,9 @@ internal class GridMeasurePolicy( val layoutWidth = constraints.constrainWidth(trackSizes.totalWidth) val layoutHeight = constraints.constrainHeight(trackSizes.totalHeight) return layout(layoutWidth, layoutHeight) { - val columnOffsets = calculateTrackOffsets(trackSizes.columnWidths) - val rowOffsets = calculateTrackOffsets(trackSizes.rowHeights) + val columnOffsets = + calculateTrackOffsets(trackSizes.columnWidths, trackSizes.columnGapPx) + val rowOffsets = calculateTrackOffsets(trackSizes.rowHeights, trackSizes.rowGapPx) resolvedGridItemsResult.gridItems.forEach { gridItem -> val placeable = gridItem.placeable // Only place if measurement succeeded (guard against edge cases) @@ -686,14 +687,28 @@ private class GridConfigurationScopeImpl(density: Density) : rowSpecs.add(size.encodedValue) } - // Gap implementation added in follow up cl - override fun gap(all: Dp) {} + override fun gap(all: Dp) { + require(all.value >= 0f) { "Gap must be non-negative" } + columnGap = all + rowGap = all + } - override fun gap(row: Dp, column: Dp) {} + override fun gap(row: Dp, column: Dp) { + require(row.value >= 0f) { "Row gap must be non-negative" } + require(column.value >= 0f) { "Column gap must be non-negative" } + rowGap = row + columnGap = column + } - override fun columnGap(gap: Dp) {} + override fun columnGap(gap: Dp) { + require(gap.value >= 0f) { "Column gap must be non-negative" } + columnGap = gap + } - override fun rowGap(gap: Dp) {} + override fun rowGap(gap: Dp) { + require(gap.value >= 0f) { "Row gap must be non-negative" } + rowGap = gap + } } /** @@ -950,6 +965,7 @@ private fun calculateGridTrackSizes( itemsByColumn = itemsByColumn, constraints = constraints, gridItems = gridItems, + columnGap = colGapPx, ) // --- Phase 2: Calculate Row Heights --- @@ -968,15 +984,19 @@ private fun calculateGridTrackSizes( constraints = constraints, columnWidths = columnWidths, gridItems = gridItems, + rowGap = rowGapPx, ) + val totalColumnGap = max(0, columnSpecs.size - 1) * colGapPx + val totalRowGap = max(0, rowSpecs.size - 1) * rowGapPx + return GridTrackSizes( columnWidths = columnWidths, rowHeights = rowHeights, columnGapPx = colGapPx, rowGapPx = rowGapPx, - totalWidth = totalTrackWidth, - totalHeight = totalTrackHeight, + totalWidth = totalTrackWidth + totalColumnGap, + totalHeight = totalTrackHeight + totalRowGap, ) } @@ -1009,6 +1029,7 @@ private fun calculateGridTrackSizes( * @param itemsByColumn Optimization lookup: List of items starting in each column index. * @param constraints Parent constraints (used for fallback behavior and cross-axis limits). * @param gridItems All items in the grid (used for spanning logic). + * @param columnGap The spacing between columns. * @return The total used width in pixels (sum of all column widths). */ private fun calculateColumnWidths( @@ -1020,10 +1041,23 @@ private fun calculateColumnWidths( itemsByColumn: Array?>, constraints: Constraints, gridItems: MutableObjectList, + columnGap: Int, ): Int { if (totalCount == 0) return 0 var totalFlex = 0f + // Calculate total space consumed by gaps. + // e.g., 3 columns have 2 gaps. (N-1) * gap. + val totalGapSpace = (columnGap * (totalCount - 1)).coerceAtLeast(0) + + // Calculate space available for actual tracks (Total - Gaps). + // If availableSpace is Infinity, availableTrackSpace value becomes Constraints.Infinity + val availableTrackSpace = + if (availableSpace == Constraints.Infinity) { + Constraints.Infinity + } else { + (availableSpace - totalGapSpace).coerceAtLeast(0) + } // Height constraint used when measuring intrinsic width. // Usually Infinity (standard intrinsic measurement), unless parent enforces strict height. @@ -1044,8 +1078,8 @@ private fun calculateColumnWidths( GridTrackSize.TypeFixed -> with(density) { spec.value.dp.roundToPx() } GridTrackSize.TypePercentage -> { - if (availableSpace != Constraints.Infinity) { - (spec.value * availableSpace).roundToInt() + if (availableTrackSpace != Constraints.Infinity) { + (spec.value * availableTrackSpace).roundToInt() } else { // If the Grid is in a horizontally scrolling container // (infinite width), we cannot calculate a percentage of "Infinity". @@ -1080,6 +1114,7 @@ private fun calculateColumnWidths( isRowAxis = false, constraints = constraints, crossAxisSizes = null, // Not needed for column width calculation + gap = columnGap, ) var usedSpace = 0 @@ -1090,7 +1125,8 @@ private fun calculateColumnWidths( // --- Pass 2: Flex Distribution --- // If we have finite width and unused space, distribute it to Flex columns. val remainingSpace = - if (availableSpace == Constraints.Infinity) 0 else max(0, availableSpace - usedSpace) + if (availableTrackSpace == Constraints.Infinity) 0 + else max(0, availableTrackSpace - usedSpace) var totalAddedFromFlex = 0 if (totalFlex > 0 && remainingSpace > 0) { @@ -1151,6 +1187,7 @@ private fun calculateColumnWidths( * @param constraints Parent constraints (used for max height limits). * @param columnWidths The resolved widths of columns. **Critical** for measuring text height. * @param gridItems All items in the grid (used for spanning logic). + * @param rowGap The spacing between rows. * @return The total used height in pixels (sum of all row heights). */ private fun calculateRowHeights( @@ -1163,10 +1200,23 @@ private fun calculateRowHeights( constraints: Constraints, columnWidths: IntArray, gridItems: MutableObjectList, + rowGap: Int, ): Int { if (totalCount == 0) return 0 var totalFlex = 0f + // Calculate total space consumed by gaps. + // e.g., 3 columns have 2 gaps. (N-1) * gap. + val totalGapSpace = (rowGap * (totalCount - 1)).coerceAtLeast(0) + + // Calculate space available for actual tracks (Total - Gaps). + // If availableSpace is Infinity, availableTrackSpace value becomes Constraints.Infinity + val availableTrackSpace = + if (availableSpace == Constraints.Infinity) { + Constraints.Infinity + } else { + (availableSpace - totalGapSpace).coerceAtLeast(0) + } // --- Pass 1: Base Sizes (Single-Span Items) --- // We iterate through every row index (both explicit and implicit). @@ -1182,8 +1232,8 @@ private fun calculateRowHeights( GridTrackSize.TypeFixed -> with(density) { spec.value.dp.roundToPx() } GridTrackSize.TypePercentage -> { - if (availableSpace != Constraints.Infinity) { - (spec.value * availableSpace).roundToInt() + if (availableTrackSpace != Constraints.Infinity) { + (spec.value * availableTrackSpace).roundToInt() } else { // If the Grid is in a vertically scrolling container // (infinite height), we cannot calculate a percentage of "Infinity". @@ -1230,6 +1280,7 @@ private fun calculateRowHeights( isRowAxis = true, constraints = constraints, crossAxisSizes = columnWidths, + gap = rowGap, ) var usedSpace = 0 @@ -1241,7 +1292,8 @@ private fun calculateRowHeights( // // If we have finite height and unused space, distribute it to Flex rows. val remainingSpace = - if (availableSpace == Constraints.Infinity) 0 else max(0, availableSpace - usedSpace) + if (availableTrackSpace == Constraints.Infinity) 0 + else max(0, availableTrackSpace - usedSpace) var totalAddedFromFlex = 0 if (totalFlex > 0 && remainingSpace > 0) { @@ -1363,6 +1415,7 @@ private fun calculateMinIntrinsicHeight( * @param crossAxisSizes The calculated sizes of the *opposite* axis (e.g., Column Widths when * calculating Row Heights). This is crucial for correctly measuring the intrinsic height of items * that wrap text based on specific column widths. + * @param gap The spacing between tracks. */ private fun distributeSpanningSpace( explicitSpecs: LongList, @@ -1371,6 +1424,7 @@ private fun distributeSpanningSpace( isRowAxis: Boolean, constraints: Constraints, crossAxisSizes: IntArray?, + gap: Int, ) { gridItems.forEach { item -> val trackIndex = if (isRowAxis) item.row else item.column @@ -1417,7 +1471,9 @@ private fun distributeSpanningSpace( for (i in colStart until colEnd) { itemWidth += crossAxisSizes[i] } - // TODO: Add gap handling + // Add the gaps that are included in the span. + val spannedGaps = max(0, item.columnSpan - 1) * gap + itemWidth += spannedGaps } else { // If we don't know column widths, constrain only by parent max. itemWidth = constraints.maxWidth @@ -1499,14 +1555,22 @@ private fun measureItems( for (i in col until colLimit) { width += trackSizes.columnWidths[i] } - // TODO: Add gaps for spanned columns + // Add gaps for spanned columns + val colSpanActual = colLimit - col + if (colSpanActual > 1) { + width += (colSpanActual - 1) * trackSizes.columnGapPx + } var height = 0 val rowLimit = (row + item.rowSpan).coerceAtMost(rowCount) for (i in row until rowLimit) { height += trackSizes.rowHeights[i] } - // TODO: Add gaps for spanned rows + // Add gaps for spanned rows + val rowSpanActual = rowLimit - row + if (rowSpanActual > 1) { + height += (rowSpanActual - 1) * trackSizes.rowGapPx + } // Use loose constraints to allow alignment to work. // If strict fixed constraints are used, child size == cell size, so alignment is @@ -1523,23 +1587,24 @@ private fun measureItems( * Computes the cumulative starting position (offset) for each track. * * This function converts a list of track sizes (e.g., column widths or row heights) into absolute - * coordinates by accumulating the size of previous tracks. + * coordinates by accumulating the size of previous tracks and the specified [gapPx] between them. * * Example logic: * - Offset[0] = 0 - * - Offset[1] = Size[0] - * - Offset[2] = Size[0] + Size[1] + * - Offset[1] = Size[0] + Gap + * - Offset[2] = Size[0] + Gap + Size[1] + Gap * * @param sizes An array containing the size of each individual track. + * @param gapPx The spacing in pixels to insert between consecutive tracks. * @return An [IntArray] of the same length as [sizes], where index `i` contains the starting * coordinate of that track. */ -private fun calculateTrackOffsets(sizes: IntArray): IntArray { +private fun calculateTrackOffsets(sizes: IntArray, gapPx: Int): IntArray { val offsets = IntArray(sizes.size) var current = 0 for (i in sizes.indices) { offsets[i] = current - current += sizes[i] + current += sizes[i] + gapPx } return offsets } From 4f7fad077f221fc0c0cbecdcea9d6fa669aa53fe Mon Sep 17 00:00:00 2001 From: Prashant Date: Wed, 10 Dec 2025 07:07:24 +0000 Subject: [PATCH 06/19] Implement Grid item alignment This commit adds support for aligning grid items within their allocated cells. Key changes: - Alignment Support: Use the `alignment` property from `Modifier.gridItem` to calculate the item's offset within its cell. - RTL Handling: The alignment logic respects the layout direction, correctly adjusting the x-offset for RTL layouts when using `place`. - Alignment Demo: Added a new demo showcasing different alignment options (TopStart, Center, BottomEnd, etc.) within grid cells. - Tests: Added unit tests to verify correct item placement with various alignments and spanning scenarios. Bug: 462550392 Test: GridTest Change-Id: I8751ddee9b516aa0d9053e56b8f741490514da5b --- .../foundation/layout/demos/GridDemo.kt | 49 ++++++ .../compose/foundation/layout/GridTest.kt | 150 ++++++++++++++++++ .../compose/foundation/layout/Grid.kt | 17 +- 3 files changed, 214 insertions(+), 2 deletions(-) diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt index b778d93c0cb4c..96716386b682c 100644 --- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt +++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt @@ -60,6 +60,7 @@ fun GridDemo() { Spacer(Modifier.height(32.dp)) GapsDemo() Spacer(Modifier.height(32.dp)) + AlignmentDemo() } } @@ -152,6 +153,54 @@ private fun GapsDemo() { } } +@Composable +private fun AlignmentDemo() { + DemoHeader("Cell Content Alignment") + Grid( + config = { + repeat(3) { column(GridTrackSize.Fixed(100.dp)) } + repeat(3) { row(GridTrackSize.Fixed(100.dp)) } + gap(4.dp) + }, + modifier = Modifier.demoContainer(borderColor = Color.Red), + ) { + val alignments = + listOf( + Alignment.TopStart to "TopStart", + Alignment.TopCenter to "TopCenter", + Alignment.TopEnd to "TopEnd", + Alignment.CenterStart to "CenterStart", + Alignment.Center to "Center", + Alignment.CenterEnd to "CenterEnd", + Alignment.BottomStart to "BottomStart", + Alignment.BottomCenter to "BottomCenter", + Alignment.BottomEnd to "BottomEnd", + ) + + alignments.forEachIndexed { index, (alignment, name) -> + val row = (index / 3) + 1 + val col = (index % 3) + 1 + + Box( + Modifier.gridItem(row, col) + .fillMaxSize() + .background(Color.LightGray.copy(alpha = 0.2f)) + .border(1.dp, Color.DarkGray.copy(alpha = 0.1f)) + ) + + Box( + Modifier.gridItem(row, col, alignment = alignment) + .size(60.dp, 40.dp) + .background(Color.Yellow.copy(alpha = 0.7f)) + .border(1.dp, Color.Red), + contentAlignment = Alignment.Center, + ) { + Text(name, fontSize = 10.sp) + } + } + } +} + @Composable private fun DemoHeader(text: String) = Text( diff --git a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt index 5c6e62c958b7c..20184e409d595 100644 --- a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt +++ b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt @@ -1226,6 +1226,156 @@ class GridTest : LayoutTest() { assertEquals(Offset(0f, (size + rowGapOverride).toFloat()), pos[2].value) } + @Test + fun testGrid_alignment() = + with(density) { + val cellSize = 100 + val itemSize = 40 + // Center: (100 - 40) / 2 = 30 + val expectedOffset = 30f + val cellSizeDp = cellSize.toDp() + val itemSizeDp = itemSize.toDp() + + val positionedLatch = CountDownLatch(1) + val childPosition = Ref() + val dummySize = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(cellSizeDp)) + row(GridTrackSize.Fixed(cellSizeDp)) + } + ) { + // Item smaller than cell, aligned center + Box( + Modifier.gridItem(1, 1, alignment = Alignment.Center) + .size(itemSizeDp) + .saveLayoutInfo(dummySize, childPosition, positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(expectedOffset, expectedOffset), childPosition.value) + } + + @Test + fun testGrid_alignment_spanning() = + with(density) { + val colSize = 50 + val rowSize = 60 + val gap = 10 + val itemSize = 40 + + // Calculate total area dimensions including the gap + val spannedWidth = (colSize * 2) + gap + val spannedHeight = (rowSize * 2) + gap + + // Alignment.BottomEnd calculation: + // x = ContainerWidth - ItemWidth + // y = ContainerHeight - ItemHeight + val expectedX = (spannedWidth - itemSize).toFloat() + val expectedY = (spannedHeight - itemSize).toFloat() + + val positionedLatch = CountDownLatch(1) + val childPosition = Ref() + val dummySize = Ref() + + show { + Grid( + config = { + repeat(2) { column(GridTrackSize.Fixed(colSize.toDp())) } + repeat(2) { row(GridTrackSize.Fixed(rowSize.toDp())) } + gap(gap.toDp()) + } + ) { + Box( + Modifier.gridItem( + 1, + 1, + rowSpan = 2, + columnSpan = 2, + alignment = Alignment.BottomEnd, + ) + .size(itemSize.toDp()) + .saveLayoutInfo(dummySize, childPosition, positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + assertEquals(Offset(expectedX, expectedY), childPosition.value) + } + + @Test + fun testGrid_alignment_rtl() = + with(density) { + val cellSize = 100 + val itemSize = 40 + val cellSizeDp = cellSize.toDp() + val itemSizeDp = itemSize.toDp() + + val positionedLatch = CountDownLatch(2) + val startAlignedPos = Ref() + val absLeftAlignedPos = Ref() + + show { + // Force RTL layout direction + androidx.compose.runtime.CompositionLocalProvider( + androidx.compose.ui.platform.LocalLayoutDirection provides + androidx.compose.ui.unit.LayoutDirection.Rtl + ) { + Grid( + config = { + column(GridTrackSize.Fixed(cellSizeDp)) + row(GridTrackSize.Fixed(cellSizeDp)) + } + ) { + // 1. Alignment.TopStart (Relative 2D Alignment) + // In RTL, "Start" is visually on the RIGHT. + // Cell Width 100. Item 40. + // Expect visual position: 100 - 40 = 60px from visual Left. + Box( + Modifier.gridItem(1, 1, alignment = Alignment.TopStart) + .size(itemSizeDp) + .saveLayoutInfo(Ref(), startAlignedPos, positionedLatch) + ) + + // 2. AbsoluteAlignment.TopLeft (Absolute 2D Alignment) + // "Left" is always visually Left, regardless of layout direction. + // Expect visual position: 0px from visual Left. + Box( + // Use TopLeft (2D) instead of Left (1D) to satisfy the Alignment + // type requirement + Modifier.gridItem( + 1, + 1, + alignment = androidx.compose.ui.AbsoluteAlignment.TopLeft, + ) + .size(itemSizeDp) + .saveLayoutInfo(Ref(), absLeftAlignedPos, positionedLatch) + ) + } + } + } + + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + // 1. Start (Right side of container) + assertEquals( + "Alignment.TopStart in RTL should be visually on the Right (60px from left)", + Offset(60f, 0f), + startAlignedPos.value, + ) + + // 2. Absolute Left (Left side of container) + assertEquals( + "AbsoluteAlignment.TopLeft in RTL should visually remain on the Left (0px)", + Offset(0f, 0f), + absLeftAlignedPos.value, + ) + } + @Test fun testGrid_nestedGrid() = with(density) { diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt index 0ca1d8f9ee8db..774fef9a862a9 100644 --- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt +++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt @@ -639,7 +639,7 @@ internal class GridMeasurePolicy( if (placeable != null) { val x = columnOffsets[gridItem.column] + gridItem.offsetX val y = rowOffsets[gridItem.row] + gridItem.offsetY - placeable.placeRelative(x, y) + placeable.place(x, y) } } } @@ -838,7 +838,7 @@ private fun resolveGridItemIndices( column = placementCol, rowSpan = rowSpan, columnSpan = colSpan, - alignment = Alignment.TopStart, // TODO Handle alignment in followup cl + alignment = data?.alignment ?: Alignment.TopStart, ) ) } @@ -1578,7 +1578,20 @@ private fun measureItems( val constraints = Constraints(maxWidth = width, maxHeight = height) val placeable = item.measurable.measure(constraints) + // Calculate Alignment Offset + val containerSize = IntSize(width, height) + val contentSize = IntSize(placeable.width, placeable.height) + val alignmentOffset = + item.alignment.align( + size = contentSize, + space = containerSize, + layoutDirection = layoutDirection, + ) + item.placeable = placeable + // Alignment.align already accounts for RTL (Start = right side) relative to 0,0. + item.offsetX = alignmentOffset.x + item.offsetY = alignmentOffset.y } } } From 2c259a32cf042eb0326c2b718d486d2c5276f28d Mon Sep 17 00:00:00 2001 From: Prashant Date: Wed, 10 Dec 2025 07:07:24 +0000 Subject: [PATCH 07/19] Implement Grid content-based sizing This commit implements intrinsic sizing for the `Grid` composable, enabling `MinContent`, `MaxContent`, and `Auto` track sizes. Added new unit tests and a demo to verify behavior for wrapping text and mixed sizing modes. Bug: 462550392 Test: GridTest Change-Id: I7cf995661f2e0b37edb08b5f238de51cba148780 --- .../foundation/layout/demos/GridDemo.kt | 23 ++++++++++ .../compose/foundation/layout/GridTest.kt | 44 +++++++++++++++++++ .../compose/foundation/layout/Grid.kt | 30 ++++++++++++- 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt index 96716386b682c..a4cbbed144406 100644 --- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt +++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt @@ -56,6 +56,8 @@ fun GridDemo() { Spacer(Modifier.height(32.dp)) FlexibleSizingDemo() Spacer(Modifier.height(32.dp)) + ContentBasedSizingDemo() + Spacer(Modifier.height(32.dp)) NegativeIndicesDemo() Spacer(Modifier.height(32.dp)) GapsDemo() @@ -112,6 +114,27 @@ private fun FlexibleSizingDemo() { } } +@Composable +private fun ContentBasedSizingDemo() { + DemoHeader("Intrinsic Sizing (Min vs Max Content)") + Grid( + config = { + column(GridTrackSize.MinContent) + column(GridTrackSize.MaxContent) + column(GridTrackSize.Flex(1.fr)) + column(GridTrackSize.Auto) + row(GridTrackSize.Auto) + gap(8.dp) + }, + modifier = Modifier.demoContainer(borderColor = Color.Black), + ) { + GridDemoItem(text = "Min Content\nWraps", row = 1, column = 1, color = Color.Red) + GridDemoItem(text = "Max Content Expands", row = 1, column = 2, color = Color.Blue) + GridDemoItem(text = "Flex Fills\nRemainder", row = 1, column = 3, color = Color.Green) + GridDemoItem(text = "Auto\nContent", row = 1, column = 4, color = Color.Yellow) + } +} + @Composable fun NegativeIndicesDemo() { DemoHeader("Negative Indices") diff --git a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt index 20184e409d595..3567e5835e488 100644 --- a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt +++ b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt @@ -1376,6 +1376,50 @@ class GridTest : LayoutTest() { ) } + @Test + fun testGrid_contentBasedSizing() { + val childSize = Ref() + val latch = CountDownLatch(1) + val dummyPosition = Ref() + + show { + Grid( + config = { + // Col 1: MinContent (should match min intrinsic width) + column(GridTrackSize.MinContent) + // Col 2: MaxContent (should match max intrinsic width) + column(GridTrackSize.MaxContent) + row(GridTrackSize.Fixed(50.dp)) + } + ) { + // Item 1: Min=50, Max=100 + IntrinsicItem( + minWidth = 50, + minIntrinsicWidth = 50, + maxIntrinsicWidth = 100, + modifier = Modifier.gridItem(1, 1).fillMaxSize(), + ) + + // Item 2: Min=50, Max=100 + IntrinsicItem( + minWidth = 50, + minIntrinsicWidth = 50, + maxIntrinsicWidth = 100, + modifier = + Modifier.gridItem(1, 2) + .fillMaxSize() + .saveLayoutInfo(childSize, dummyPosition, latch), + ) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Col 1 should be 50 (min) + // Col 2 should be 100 (max) + // Item 2 in Col 2 should have width 100 + assertEquals(100, childSize.value?.width) + } + @Test fun testGrid_nestedGrid() = with(density) { diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt index 774fef9a862a9..d15812b7b9119 100644 --- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt +++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt @@ -1097,7 +1097,14 @@ private fun calculateColumnWidths( calculateMinIntrinsicWidth(itemsByColumn[index], crossAxisAvailable) } - // Auto, MinContent, MaxContent (Implicit tracks fall here) + GridTrackSize.TypeMinContent -> + calculateMinIntrinsicWidth(itemsByColumn[index], crossAxisAvailable) + GridTrackSize.TypeMaxContent -> + calculateMaxIntrinsicWidth(itemsByColumn[index], crossAxisAvailable) + // Auto typically behaves like MaxContent in most contexts (fit the content + // comfortably). + GridTrackSize.TypeAuto -> + calculateMaxIntrinsicWidth(itemsByColumn[index], crossAxisAvailable) // Measure the max intrinsic width of all items in this column. else -> calculateMaxIntrinsicWidth(itemsByColumn[index], crossAxisAvailable) } @@ -1259,7 +1266,26 @@ private fun calculateRowHeights( ) } - // Auto, MinContent, MaxContent (Implicit tracks fall here) + GridTrackSize.TypeMinContent -> + calculateMinIntrinsicHeight( + items = itemsByRow[index], + columnWidths = columnWidths, + fallbackWidth = constraints.maxWidth, + ) + GridTrackSize.TypeMaxContent -> + calculateMaxIntrinsicHeight( + items = itemsByRow[index], + columnWidths = columnWidths, + fallbackWidth = constraints.maxWidth, + ) + // Auto typically behaves like MaxContent in most contexts (fit the content + // comfortably). + GridTrackSize.TypeAuto -> + calculateMaxIntrinsicHeight( + items = itemsByRow[index], + columnWidths = columnWidths, + fallbackWidth = constraints.maxWidth, + ) else -> calculateMaxIntrinsicHeight( items = itemsByRow[index], From 71e2e9a54e07cd19eba3e49a6a00fae1191e5530 Mon Sep 17 00:00:00 2001 From: Prashant Date: Wed, 10 Dec 2025 12:32:27 +0000 Subject: [PATCH 08/19] Implement Grid auto-placement This commit implements the auto-placement algorithm for the `Grid` composable, allowing items without explicit row/column coordinates to automatically flow into available cells. Added `AutoPlacementDemo` and unit tests to verify flow direction, wrapping behavior, and implicit track creation. Bug: 462550392 Test: GridTest Change-Id: I2c988e2f444344202448b05ae4044b0832521d87 --- .../foundation/layout/demos/GridDemo.kt | 176 +- .../compose/foundation/layout/GridTest.kt | 1978 ++++++++++++----- .../compose/foundation/layout/Grid.kt | 166 +- 3 files changed, 1698 insertions(+), 622 deletions(-) diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt index a4cbbed144406..7a45cb048ad72 100644 --- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt +++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt @@ -17,11 +17,14 @@ package androidx.compose.foundation.layout.demos import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Grid +import androidx.compose.foundation.layout.GridFlow import androidx.compose.foundation.layout.GridScope import androidx.compose.foundation.layout.GridTrackSize +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.columns import androidx.compose.foundation.layout.fillMaxSize @@ -43,6 +46,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.IntSize @@ -60,9 +64,78 @@ fun GridDemo() { Spacer(Modifier.height(32.dp)) NegativeIndicesDemo() Spacer(Modifier.height(32.dp)) - GapsDemo() + GapsAndContentDemo() Spacer(Modifier.height(32.dp)) AlignmentDemo() + Spacer(Modifier.height(32.dp)) + AutoPlacementDemo() + Spacer(Modifier.height(32.dp)) + InfiniteConstraintsDemo() + Spacer(Modifier.height(32.dp)) + MinContentSafetyDemo() + } +} + +@Composable +private fun AutoPlacementDemo() { + DemoHeader("Auto Placement & Flow") + + Text( + "1. Flow = Row (Fixed Cols, Implicit Rows)", + fontSize = 12.sp, + fontStyle = FontStyle.Italic, + modifier = Modifier.padding(bottom = 4.dp), + ) + Grid( + config = { + repeat(3) { column(GridTrackSize.Fixed(80.dp)) } // 3 Explicit Columns + gap(4.dp) + flow = GridFlow.Row // Default + }, + modifier = Modifier.demoContainer(borderColor = Color.Blue), + ) { + // 1. Simple auto items + repeat(3) { index -> + GridDemoItem(text = "${index + 1}", color = Color.Cyan, measureSize = false) + } + + // 2. Auto item with Span (Takes 2 spots) + GridDemoItem(text = "Span 2", columnSpan = 2, color = Color.Magenta, measureSize = false) + + // 3. More auto items (Wrapping to next row) + repeat(2) { index -> + GridDemoItem(text = "${index + 5}", color = Color.Cyan, measureSize = false) + } + } + + Spacer(Modifier.height(24.dp)) + + Text( + "2. Flow = Column (Fixed Rows, Implicit Cols)", + fontSize = 12.sp, + fontStyle = FontStyle.Italic, + modifier = Modifier.padding(bottom = 4.dp), + ) + // Scrollable container to allow implicit columns to grow horizontally + Box(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState())) { + Grid( + config = { + flow = GridFlow.Column + repeat(3) { row(50.dp) } // 3 Explicit Rows + gap(4.dp) + }, + modifier = Modifier.border(1.dp, Color.Magenta.copy(alpha = 0.5f)).padding(8.dp), + ) { + repeat(10) { index -> + GridDemoItem( + text = "${index + 1}", + color = if (index % 2 == 0) Color.Yellow else Color.Green, + measureSize = false, + // Explicit size helps visualization in 'Auto' implicit tracks + modifier = Modifier.size(60.dp, 40.dp), + ) + } + } } } @@ -114,6 +187,25 @@ private fun FlexibleSizingDemo() { } } +@Composable +fun NegativeIndicesDemo() { + DemoHeader("Negative Indices") + Grid( + config = { + repeat(3) { column(60.dp) } + repeat(3) { row(60.dp) } + gap(4.dp) + }, + modifier = Modifier.border(1.dp, Color.Gray), + ) { + GridDemoItem(text = "TL", row = 1, column = 1, color = Color.Red) + GridDemoItem("TR", row = 1, column = -1, color = Color.Blue) + GridDemoItem("BL", row = -1, column = 1, color = Color.Green) + GridDemoItem("BR", row = -1, column = -1, color = Color.Yellow) + GridDemoItem("Center", row = 2, column = 2, color = Color.Gray) + } +} + @Composable private fun ContentBasedSizingDemo() { DemoHeader("Intrinsic Sizing (Min vs Max Content)") @@ -136,27 +228,8 @@ private fun ContentBasedSizingDemo() { } @Composable -fun NegativeIndicesDemo() { - DemoHeader("Negative Indices") - Grid( - config = { - repeat(3) { column(60.dp) } - repeat(3) { row(60.dp) } - gap(4.dp) - }, - modifier = Modifier.border(1.dp, Color.Gray), - ) { - GridDemoItem(text = "TL", row = 1, column = 1, color = Color.Red) - GridDemoItem("TR", row = 1, column = -1, color = Color.Blue) - GridDemoItem("BL", row = -1, column = 1, color = Color.Green) - GridDemoItem("BR", row = -1, column = -1, color = Color.Yellow) - GridDemoItem("Center", row = 2, column = 2, color = Color.Gray) - } -} - -@Composable -private fun GapsDemo() { - DemoHeader("Gaps Demo") +private fun GapsAndContentDemo() { + DemoHeader("Gaps & Content Sizing") Grid( config = { column(GridTrackSize.Fixed(100.dp)) @@ -224,6 +297,65 @@ private fun AlignmentDemo() { } } +@Composable +private fun InfiniteConstraintsDemo() { + DemoHeader("Infinite Constraints") + Text( + "Percentage tracks fall back to Auto sizing when placed in an infinite container.", + fontSize = 12.sp, + fontStyle = FontStyle.Italic, + modifier = Modifier.padding(bottom = 4.dp), + ) + + Row( + Modifier.fillMaxWidth() + .border(1.dp, Color.Gray) + .padding(8.dp) + .horizontalScroll(rememberScrollState()) + ) { + Grid( + config = { + column(GridTrackSize.Percentage(0.5f)) + row(GridTrackSize.Auto) + gap(4.dp) + }, + modifier = Modifier.border(1.dp, Color.Blue), + ) { + GridDemoItem( + text = "I am 150dp wide\n(Percentage -> Auto)", + modifier = Modifier.width(150.dp), + color = Color.Cyan, + ) + } + } +} + +@Composable +private fun MinContentSafetyDemo() { + DemoHeader("Min-Content Flex") + Text( + "Flex tracks implement minmax(min-content, 1fr).", + fontSize = 12.sp, + fontStyle = FontStyle.Italic, + modifier = Modifier.padding(bottom = 4.dp), + ) + Box(Modifier.width(50.dp).border(2.dp, Color.Red).padding(2.dp)) { + Grid( + config = { + column(GridTrackSize.Flex(1.fr)) + row(GridTrackSize.Auto) + }, + modifier = Modifier.border(1.dp, Color.Green), + ) { + GridDemoItem( + text = "Min 120dp", + modifier = Modifier.width(120.dp), + color = Color.Magenta, + ) + } + } +} + @Composable private fun DemoHeader(text: String) = Text( diff --git a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt index 3567e5835e488..968757438be9d 100644 --- a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt +++ b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt @@ -16,6 +16,8 @@ package androidx.compose.foundation.layout +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.rememberScrollState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -369,6 +371,42 @@ class GridTest : LayoutTest() { assertEquals(Offset((fixedWidth + 50).toFloat(), 0f), childPosition[2].value) } + @Test + fun testGrid_flexRespectsMinContent() = + with(density) { + val minContentSize = 100 + val totalSize = 150 // Only 50px left for flex + val latch = CountDownLatch(2) + val sizes = Array(2) { Ref() } + + show { + Grid( + config = { + column(GridTrackSize.MinContent) + column(GridTrackSize.Flex(1.fr)) + row(GridTrackSize.Fixed(50.dp)) + }, + modifier = Modifier.width(totalSize.toDp()), + ) { + // Item 1 (MinContent): Needs 100px + IntrinsicItem( + minWidth = minContentSize, + minIntrinsicWidth = minContentSize, + maxIntrinsicWidth = minContentSize, + modifier = Modifier.gridItem(1, 1).saveLayoutInfo(sizes[0], Ref(), latch), + ) + // Item 2 (Flex): Gets remaining 50px + Box( + Modifier.gridItem(1, 2).fillMaxSize().saveLayoutInfo(sizes[1], Ref(), latch) + ) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(minContentSize, sizes[0].value?.width) + assertEquals(totalSize - minContentSize, sizes[1].value?.width) + } + @Test fun testGrid_flexTrack_minContent_lowerBound() = with(density) { @@ -477,254 +515,1192 @@ class GridTest : LayoutTest() { } @Test - fun testGrid_rowSpan_expandsRowsToFitContent() = + fun testGrid_mixedTrackTypes_resolutionOrder() = with(density) { - // Scenario: - // 2 Columns (Fixed 50) - // 2 Rows (Auto) - // Item 1 (Col 1, Row 1): Small (10px height) - // Item 2 (Col 1, Row 2): Small (10px height) - // Item 3 (Col 2, Row 1, Span 2): Tall (100px height) - // - // Expected Behavior: - // Without spanning logic: Rows would be 10px each (total 20px). Item 3 would overflow. - // With spanning logic: Item 3 needs 100px. - // Deficit = 100 - (10 + 10) = 80px. - // Distribute 80px / 2 rows = +40px each. - // Final Row Heights: 10 + 40 = 50px each. - // Total Grid Height: 100px. - - val colWidth = 50.dp - val smallItemHeight = 10.dp - val tallItemHeight = 100.dp - val expectedTotalHeight = 100.dp.roundToPx() + val fixedSize = 50 + val contentSize = 30 + val totalSize = 200 // 50 (Fixed) + 30 (Auto) + Flex = 200 -> Flex = 120 - val latch = CountDownLatch(1) - val gridSize = Ref() + val latch = CountDownLatch(3) + val sizes = Array(3) { Ref() } + val positions = Array(3) { Ref() } show { Grid( config = { - column(GridTrackSize.Fixed(colWidth)) - column(GridTrackSize.Fixed(colWidth)) - row(GridTrackSize.Auto) - row(GridTrackSize.Auto) + column(GridTrackSize.Fixed(fixedSize.toDp())) // Col 1: Fixed + column(GridTrackSize.Auto) // Col 2: Auto (Content based) + column(GridTrackSize.Flex(1.fr)) // Col 3: Flex (Remaining) + row(GridTrackSize.Fixed(50.dp)) }, - modifier = - Modifier.onGloballyPositioned { - gridSize.value = it.size - latch.countDown() - }, + modifier = Modifier.width(totalSize.toDp()), ) { - // Col 1, Row 1 - Box(Modifier.gridItem(1, 1).size(colWidth, smallItemHeight)) - // Col 1, Row 2 - Box(Modifier.gridItem(2, 1).size(colWidth, smallItemHeight)) - - // Col 2, Row 1, Span 2 (The driver of expansion) + // Col 1: Fixed item Box( - Modifier.gridItem(row = 1, column = 2, rowSpan = 2) - .size(colWidth, tallItemHeight) + Modifier.gridItem(1, 1) + .fillMaxSize() + .saveLayoutInfo(sizes[0], positions[0], latch) ) - } - } - - assertTrue(latch.await(1, TimeUnit.SECONDS)) - - assertEquals( - "Grid height should expand to accommodate the tall row-spanning item", - expectedTotalHeight, - gridSize.value?.height, - ) - } - - @Test - fun testGrid_rowSpan_expandsFlexRows() = - with(density) { - // Scenario: - // Container Height = 100px (Fixed constraint) - // 2 Rows (Flex 1fr) - // Item 1 (Row 1): Empty - // Item 2 (Row 2): Empty - // Item 3 (Span 2): Tall (200px) - // - // Expected Behavior: - // Flex logic initially splits 100px -> 50px each. - // Spanning logic sees Item 3 needs 200px. - // Deficit = 200 - 100 = 100px. - // Rows should grow to 100px each. - // Total Height = 200px (Grid expands beyond parent constraint if content demands it). - - val containerHeight = 100.dp - val tallItemHeight = 200.dp - val expectedTotalHeight = 200.dp.roundToPx() - val latch = CountDownLatch(1) - val gridSize = Ref() + // Col 2: Auto item (determines track width) + Box( + Modifier.gridItem(1, 2) + .width(contentSize.toDp()) + .fillMaxHeight() + .saveLayoutInfo(sizes[1], positions[1], latch) + ) - show { - // Wrap in a box with fixed height to simulate constraints, - // but allow Grid to be larger (unbounded internal checks) - Box( - Modifier.height(containerHeight) - .wrapContentHeight(align = Alignment.Top, unbounded = true) - ) { - Grid( - config = { - column(GridTrackSize.Fixed(50.dp)) - row(GridTrackSize.Flex(1.fr)) - row(GridTrackSize.Flex(1.fr)) - }, - modifier = - Modifier.onGloballyPositioned { - gridSize.value = it.size - latch.countDown() - }, - ) { - // Spanning item forcing expansion - Box( - Modifier.gridItem(row = 1, column = 1, rowSpan = 2) - .size(50.dp, tallItemHeight) - ) - } + // Col 3: Flex item + Box( + Modifier.gridItem(1, 3) + .fillMaxSize() + .saveLayoutInfo(sizes[2], positions[2], latch) + ) } } - assertTrue(latch.await(1, TimeUnit.SECONDS)) - assertEquals( - "Flex rows should expand beyond 1fr share if spanning item requires it", - expectedTotalHeight, - gridSize.value?.height, - ) + // Col 1: 50 + assertEquals(fixedSize, sizes[0].value?.width) + // Col 2: 30 (sized by content) + assertEquals(contentSize, sizes[1].value?.width) + // Col 3: 200 - 50 - 30 = 120 + assertEquals(120, sizes[2].value?.width) } @Test - fun testGrid_explicitPlacement_allowsOverlaps() = + fun testGrid_gaps() = with(density) { - // Scenario: - // Two items explicitly placed in (1, 1). - // They should occupy the same space. The grid should not throw or shift them. - val size = 50 + val gap = 10 + val gapDp = gap.toDp() val sizeDp = size.toDp() - val latch = CountDownLatch(2) - val pos = Array(2) { Ref() } - val dummy = Ref() + + val positionedLatch = CountDownLatch(2) + val childPosition = Array(2) { Ref() } + // Use explicit dummy refs instead of passing null to avoid overload ambiguity + val dummySize = Array(2) { Ref() } show { Grid( config = { + column(GridTrackSize.Fixed(sizeDp)) column(GridTrackSize.Fixed(sizeDp)) row(GridTrackSize.Fixed(sizeDp)) + gap(gapDp) } ) { - // Item 1 - Box(Modifier.gridItem(1, 1).size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) - // Item 2 (Same Cell) - Box(Modifier.gridItem(1, 1).size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) - } - } - - assertTrue(latch.await(1, TimeUnit.SECONDS)) - - assertEquals(Offset(0f, 0f), pos[0].value) - assertEquals(Offset(0f, 0f), pos[1].value) - } - - @Test - fun testGrid_negativeIndices_placeCorrectly() = - with(density) { - val size = 50 - val sizeDp = size.toDp() - - val positionedLatch = CountDownLatch(4) - val childPosition = Array(4) { Ref() } - val dummySize = Array(4) { Ref() } - - show { - Grid( - config = { - repeat(3) { column(GridTrackSize.Fixed(sizeDp)) } - repeat(3) { row(GridTrackSize.Fixed(sizeDp)) } - } - ) { - // 1. Top-Left (1, 1) -> (0, 0) + // Item 1: (0, 0) Box( Modifier.gridItem(1, 1) .fillMaxSize() .saveLayoutInfo(dummySize[0], childPosition[0], positionedLatch) ) - // 2. Top-Right (1, -1) -> (100, 0) (Last Column) + // Item 2: (50 + 10, 0) = (60, 0) Box( - Modifier.gridItem(1, -1) + Modifier.gridItem(1, 2) .fillMaxSize() .saveLayoutInfo(dummySize[1], childPosition[1], positionedLatch) ) - // 3. Bottom-Left (-1, 1) -> (0, 100) (Last Row) - Box( - Modifier.gridItem(-1, 1) - .fillMaxSize() - .saveLayoutInfo(dummySize[2], childPosition[2], positionedLatch) - ) - // 4. Bottom-Right (-1, -1) -> (100, 100) (Last Row, Last Column) - Box( - Modifier.gridItem(-1, -1) - .fillMaxSize() - .saveLayoutInfo(dummySize[3], childPosition[3], positionedLatch) - ) } } assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) - // 1. (0, 0) assertEquals(Offset(0f, 0f), childPosition[0].value) - // 2. (100, 0) -> Col index 2 * 50 - assertEquals(Offset((size * 2).toFloat(), 0f), childPosition[1].value) - // 3. (0, 100) -> Row index 2 * 50 - assertEquals(Offset(0f, (size * 2).toFloat()), childPosition[2].value) - // 4. (100, 100) - assertEquals(Offset((size * 2).toFloat(), (size * 2).toFloat()), childPosition[3].value) + assertEquals(Offset((size + gap).toFloat(), 0f), childPosition[1].value) } @Test - fun testGrid_invalidNegativeIndices_fallbackToAuto() = + fun testGrid_flexWithGaps() = with(density) { val size = 50 - val sizeDp = size.toDp() - val latch = CountDownLatch(2) - val pos1 = Ref() - val pos2 = Ref() - val dummy = Ref() + val gap = 10 + // Available space for tracks: 50 - 10 = 40. + // 1.fr + 1.fr = 2 parts. 40 / 2 = 20 per track. + val expectedColWidth = (size - gap) / 2 + + val positionedLatch = CountDownLatch(2) + val childSize = Array(2) { Ref() } + val childPosition = Array(2) { Ref() } show { Grid( config = { - // 2x2 Grid - repeat(2) { column(GridTrackSize.Fixed(sizeDp)) } - repeat(2) { row(GridTrackSize.Fixed(sizeDp)) } - } + column(GridTrackSize.Flex(1.fr)) + column(GridTrackSize.Flex(1.fr)) + row(GridTrackSize.Fixed(size.toDp())) + gap(gap.toDp()) + }, + modifier = Modifier.requiredSize(size.toDp()), ) { - // Case 1: Valid Negative (-1 -> Index 1) Box( - Modifier.gridItem(row = -1, column = -1) - .size(sizeDp) - .saveLayoutInfo(dummy, pos1, latch) + Modifier.gridItem(1, 1) + .fillMaxSize() + .saveLayoutInfo(childSize[0], childPosition[0], positionedLatch) ) - - // Case 2: Invalid Negative (-5 -> Index -3 -> Invalid) - // Should be treated as "Unspecified" and auto-placed. - // Since (1,1) is empty (Item 1 is at 1,1 0-based), it should go to (0,0). Box( - Modifier.gridItem(row = -5, column = -5) - .size(sizeDp) - .saveLayoutInfo(dummy, pos2, latch) + Modifier.gridItem(1, 2) + .fillMaxSize() + .saveLayoutInfo(childSize[1], childPosition[1], positionedLatch) ) } } - assertTrue(latch.await(1, TimeUnit.SECONDS)) - - // Item 1 (Valid -1,-1): Bottom-Right (50, 50) + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + assertEquals(IntSize(expectedColWidth, size), childSize[0].value) + assertEquals(IntSize(expectedColWidth, size), childSize[1].value) + assertEquals(Offset(0f, 0f), childPosition[0].value) + assertEquals(Offset((expectedColWidth + gap).toFloat(), 0f), childPosition[1].value) + } + + @Test + fun testGrid_spanningWithGaps() { + val size = 50 + val gap = 10 + // Spanning 2 columns: size + gap + size = 50 + 10 + 50 = 110 + val expectedWidth = size * 2 + gap + + val positionedLatch = CountDownLatch(1) + val childSize = Ref() + val dummyPos = Ref() + + show { + Grid( + config = { + repeat(3) { column(GridTrackSize.Fixed(size.toDp())) } + row(GridTrackSize.Fixed(size.toDp())) + gap(gap.toDp()) + } + ) { + Box( + Modifier.gridItem(row = 1, column = 1, columnSpan = 2) + .fillMaxSize() + .saveLayoutInfo(childSize, dummyPos, positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + assertEquals(IntSize(expectedWidth, size), childSize.value) + } + + @Test + fun testGrid_implicitTracks_respectGaps() = + with(density) { + val size = 50 + val gap = 10 + val latch = CountDownLatch(2) + val pos = Array(2) { Ref() } + + // Create a dummy ref instead of passing null + val dummy = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(size.toDp())) // 1 Explicit Col + row(GridTrackSize.Fixed(size.toDp())) + gap(gap.toDp()) + } + ) { + // Item 1: Col 1 + Box( + Modifier.gridItem(1, 1) + .size(size.toDp()) + .saveLayoutInfo(dummy, pos[0], latch) + ) + // Item 2: Col 2 (Implicit). Should be at 50 + 10 = 60. + Box( + Modifier.gridItem(1, 2) + .size(size.toDp()) + .saveLayoutInfo(dummy, pos[1], latch) + ) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset((size + gap).toFloat(), 0f), pos[1].value) + } + + @Test + fun testGrid_gapPrecedence_specificOverridesGeneric() = + with(density) { + // Scenario: + // gap(10) sets both. + // rowGap(20) overrides row gap. + // columnGap(5) overrides column gap. + // Result: RowGap = 20, ColumnGap = 5. + + val size = 50 + val baseGap = 10 + val rowGapOverride = 20 + val colGapOverride = 5 + + val sizeDp = size.toDp() + val latch = CountDownLatch(3) + val pos = Array(3) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + repeat(2) { column(GridTrackSize.Fixed(sizeDp)) } + repeat(2) { row(GridTrackSize.Fixed(sizeDp)) } + + gap(baseGap.toDp()) // Sets both to 10 + rowGap(rowGapOverride.toDp()) // Overrides row to 20 + columnGap(colGapOverride.toDp()) // Overrides col to 5 + } + ) { + // (0,0) + Box(Modifier.gridItem(1, 1).size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) + // (0,1) -> X should be Size + ColGap(5) + Box(Modifier.gridItem(1, 2).size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) + // (1,0) -> Y should be Size + RowGap(20) + Box(Modifier.gridItem(2, 1).size(sizeDp).saveLayoutInfo(dummy, pos[2], latch)) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset((size + colGapOverride).toFloat(), 0f), pos[1].value) + assertEquals(Offset(0f, (size + rowGapOverride).toFloat()), pos[2].value) + } + + @Test + fun testGrid_alignment() = + with(density) { + val cellSize = 100 + val itemSize = 40 + // Center: (100 - 40) / 2 = 30 + val expectedOffset = 30f + val cellSizeDp = cellSize.toDp() + val itemSizeDp = itemSize.toDp() + + val positionedLatch = CountDownLatch(1) + val childPosition = Ref() + val dummySize = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(cellSizeDp)) + row(GridTrackSize.Fixed(cellSizeDp)) + } + ) { + // Item smaller than cell, aligned center + Box( + Modifier.gridItem(1, 1, alignment = Alignment.Center) + .size(itemSizeDp) + .saveLayoutInfo(dummySize, childPosition, positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(expectedOffset, expectedOffset), childPosition.value) + } + + @Test + fun testGrid_alignment_spanning() = + with(density) { + val colSize = 50 + val rowSize = 60 + val gap = 10 + val itemSize = 40 + + // Calculate total area dimensions including the gap + val spannedWidth = (colSize * 2) + gap + val spannedHeight = (rowSize * 2) + gap + + // Alignment.BottomEnd calculation: + // x = ContainerWidth - ItemWidth + // y = ContainerHeight - ItemHeight + val expectedX = (spannedWidth - itemSize).toFloat() + val expectedY = (spannedHeight - itemSize).toFloat() + + val positionedLatch = CountDownLatch(1) + val childPosition = Ref() + val dummySize = Ref() + + show { + Grid( + config = { + repeat(2) { column(GridTrackSize.Fixed(colSize.toDp())) } + repeat(2) { row(GridTrackSize.Fixed(rowSize.toDp())) } + gap(gap.toDp()) + } + ) { + Box( + Modifier.gridItem( + 1, + 1, + rowSpan = 2, + columnSpan = 2, + alignment = Alignment.BottomEnd, + ) + .size(itemSize.toDp()) + .saveLayoutInfo(dummySize, childPosition, positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + assertEquals(Offset(expectedX, expectedY), childPosition.value) + } + + @Test + fun testGrid_alignment_rtl() = + with(density) { + val cellSize = 100 + val itemSize = 40 + val cellSizeDp = cellSize.toDp() + val itemSizeDp = itemSize.toDp() + + val positionedLatch = CountDownLatch(2) + val startAlignedPos = Ref() + val absLeftAlignedPos = Ref() + + show { + // Force RTL layout direction + androidx.compose.runtime.CompositionLocalProvider( + androidx.compose.ui.platform.LocalLayoutDirection provides + androidx.compose.ui.unit.LayoutDirection.Rtl + ) { + Grid( + config = { + column(GridTrackSize.Fixed(cellSizeDp)) + row(GridTrackSize.Fixed(cellSizeDp)) + } + ) { + // 1. Alignment.TopStart (Relative 2D Alignment) + // In RTL, "Start" is visually on the RIGHT. + // Cell Width 100. Item 40. + // Expect visual position: 100 - 40 = 60px from visual Left. + Box( + Modifier.gridItem(1, 1, alignment = Alignment.TopStart) + .size(itemSizeDp) + .saveLayoutInfo(Ref(), startAlignedPos, positionedLatch) + ) + + // 2. AbsoluteAlignment.TopLeft (Absolute 2D Alignment) + // "Left" is always visually Left, regardless of layout direction. + // Expect visual position: 0px from visual Left. + Box( + // Use TopLeft (2D) instead of Left (1D) to satisfy the Alignment + // type requirement + Modifier.gridItem( + 1, + 1, + alignment = androidx.compose.ui.AbsoluteAlignment.TopLeft, + ) + .size(itemSizeDp) + .saveLayoutInfo(Ref(), absLeftAlignedPos, positionedLatch) + ) + } + } + } + + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + // 1. Start (Right side of container) + assertEquals( + "Alignment.TopStart in RTL should be visually on the Right (60px from left)", + Offset(60f, 0f), + startAlignedPos.value, + ) + + // 2. Absolute Left (Left side of container) + assertEquals( + "AbsoluteAlignment.TopLeft in RTL should visually remain on the Left (0px)", + Offset(0f, 0f), + absLeftAlignedPos.value, + ) + } + + @Test + fun testGrid_contentBasedSizing() { + val childSize = Ref() + val latch = CountDownLatch(1) + val dummyPosition = Ref() + + show { + Grid( + config = { + // Col 1: MinContent (should match min intrinsic width) + column(GridTrackSize.MinContent) + // Col 2: MaxContent (should match max intrinsic width) + column(GridTrackSize.MaxContent) + row(GridTrackSize.Fixed(50.dp)) + } + ) { + // Item 1: Min=50, Max=100 + IntrinsicItem( + minWidth = 50, + minIntrinsicWidth = 50, + maxIntrinsicWidth = 100, + modifier = Modifier.gridItem(1, 1).fillMaxSize(), + ) + + // Item 2: Min=50, Max=100 + IntrinsicItem( + minWidth = 50, + minIntrinsicWidth = 50, + maxIntrinsicWidth = 100, + modifier = + Modifier.gridItem(1, 2) + .fillMaxSize() + .saveLayoutInfo(childSize, dummyPosition, latch), + ) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Col 1 should be 50 (min) + // Col 2 should be 100 (max) + // Item 2 in Col 2 should have width 100 + assertEquals(100, childSize.value?.width) + } + + @Test + fun testGrid_implicitTracks_shrinkToFitContent() = + with(density) { + // Scenario: + // Item placed in Col 10. + // Cols 1-9 should be implicit Auto. + // If they have no content, they should have width 0. + // Col 10 should have width 50. + // Total Grid Width should be 50 (0+0...+50). + + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(1) + val pos = Ref() + val dummy = Ref() + + show { + Grid( + config = { + // No explicit columns + row(GridTrackSize.Fixed(sizeDp)) + } + ) { + // Place far away at Column 5. + // Columns 1, 2, 3, 4 are Implicit Auto and Empty -> Size 0. + Box( + Modifier.gridItem(column = 5).size(sizeDp).saveLayoutInfo(dummy, pos, latch) + ) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Position should be 0 because previous columns collapsed. + // Note: If gaps were added, they would add up (4 * gap). + assertEquals(Offset(0f, 0f), pos.value) + } + + @Test + fun testGrid_rowSpan_expandsRowsToFitContent() = + with(density) { + // Scenario: + // 2 Columns (Fixed 50) + // 2 Rows (Auto) + // Item 1 (Col 1, Row 1): Small (10px height) + // Item 2 (Col 1, Row 2): Small (10px height) + // Item 3 (Col 2, Row 1, Span 2): Tall (100px height) + // + // Expected Behavior: + // Without spanning logic: Rows would be 10px each (total 20px). Item 3 would overflow. + // With spanning logic: Item 3 needs 100px. + // Deficit = 100 - (10 + 10) = 80px. + // Distribute 80px / 2 rows = +40px each. + // Final Row Heights: 10 + 40 = 50px each. + // Total Grid Height: 100px. + + val colWidth = 50.dp + val smallItemHeight = 10.dp + val tallItemHeight = 100.dp + val expectedTotalHeight = 100.dp.roundToPx() + + val latch = CountDownLatch(1) + val gridSize = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(colWidth)) + column(GridTrackSize.Fixed(colWidth)) + row(GridTrackSize.Auto) + row(GridTrackSize.Auto) + }, + modifier = + Modifier.onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + // Col 1, Row 1 + Box(Modifier.gridItem(1, 1).size(colWidth, smallItemHeight)) + // Col 1, Row 2 + Box(Modifier.gridItem(2, 1).size(colWidth, smallItemHeight)) + + // Col 2, Row 1, Span 2 (The driver of expansion) + Box( + Modifier.gridItem(row = 1, column = 2, rowSpan = 2) + .size(colWidth, tallItemHeight) + ) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals( + "Grid height should expand to accommodate the tall row-spanning item", + expectedTotalHeight, + gridSize.value?.height, + ) + } + + @Test + fun testGrid_rowSpan_expandsFlexRows() = + with(density) { + // Scenario: + // Container Height = 100px (Fixed constraint) + // 2 Rows (Flex 1fr) + // Item 1 (Row 1): Empty + // Item 2 (Row 2): Empty + // Item 3 (Span 2): Tall (200px) + // + // Expected Behavior: + // Flex logic initially splits 100px -> 50px each. + // Spanning logic sees Item 3 needs 200px. + // Deficit = 200 - 100 = 100px. + // Rows should grow to 100px each. + // Total Height = 200px (Grid expands beyond parent constraint if content demands it). + + val containerHeight = 100.dp + val tallItemHeight = 200.dp + val expectedTotalHeight = 200.dp.roundToPx() + + val latch = CountDownLatch(1) + val gridSize = Ref() + + show { + // Wrap in a box with fixed height to simulate constraints, + // but allow Grid to be larger (unbounded internal checks) + Box( + Modifier.height(containerHeight) + .wrapContentHeight(align = Alignment.Top, unbounded = true) + ) { + Grid( + config = { + column(GridTrackSize.Fixed(50.dp)) + row(GridTrackSize.Flex(1.fr)) + row(GridTrackSize.Flex(1.fr)) + }, + modifier = + Modifier.onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + // Spanning item forcing expansion + Box( + Modifier.gridItem(row = 1, column = 1, rowSpan = 2) + .size(50.dp, tallItemHeight) + ) + } + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals( + "Flex rows should expand beyond 1fr share if spanning item requires it", + expectedTotalHeight, + gridSize.value?.height, + ) + } + + @Test + fun testGrid_autoPlacement_rowFlow() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(3) + val pos = Array(3) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(sizeDp)) + column(GridTrackSize.Fixed(sizeDp)) + // Rows are implicit/auto + flow = GridFlow.Row + } + ) { + // We use Modifier.size because implicit Auto tracks need content size to expand + // Item 1 -> (0,0) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) + // Item 2 -> (50,0) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) + // Item 3 -> Wraps to next row -> (0, 50) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[2], latch)) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset(size.toFloat(), 0f), pos[1].value) + assertEquals(Offset(0f, size.toFloat()), pos[2].value) + } + + @Test + fun testGrid_autoPlacement_columnFlow() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(3) + val pos = Array(3) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + flow = GridFlow.Column + row(GridTrackSize.Fixed(sizeDp)) + row(GridTrackSize.Fixed(sizeDp)) + // Cols are implicit/auto + } + ) { + // Item 1 -> (0,0) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) + // Item 2 -> (0,50) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) + // Item 3 -> Wraps to next col -> (50,0) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[2], latch)) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset(0f, size.toFloat()), pos[1].value) + assertEquals(Offset(size.toFloat(), 0f), pos[2].value) + } + + @Test + fun testGrid_autoPlacement_wrapping_respectsGaps() = + with(density) { + val size = 50 + val gap = 10 + val sizeDp = size.toDp() + val gapDp = gap.toDp() + val latch = CountDownLatch(2) + val pos = Array(2) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(sizeDp)) // Only 1 column + row(GridTrackSize.Fixed(sizeDp)) + gap(gapDp) + flow = GridFlow.Row + } + ) { + // Item 1: (0,0) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) + // Item 2: Wraps to (0,1). Should include vertical gap. + // Y Position = Row 1 Height (50) + Gap (10) = 60 + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset(0f, (size + gap).toFloat()), pos[1].value) + } + + @Test + fun testGrid_columnFlow_wrapsCorrectly() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(3) + val pos = Array(3) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + flow = GridFlow.Column + // 2 Explicit Rows + row(GridTrackSize.Fixed(sizeDp)) + row(GridTrackSize.Fixed(sizeDp)) + } + ) { + // Item 1 -> (0,0) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) + // Item 2 -> (0,50) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) + // Item 3 -> Wraps to Next Column -> (50,0) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[2], latch)) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset(0f, size.toFloat()), pos[1].value) + assertEquals(Offset(size.toFloat(), 0f), pos[2].value) + } + + @Test + fun testGrid_columnFlow_createsImplicitColumns() = + with(density) { + val itemSize = 50 + val latch = CountDownLatch(1) + val gridSize = Ref() + + show { + Grid( + config = { + // 2 Explicit Rows. Flow = Column. + row(GridTrackSize.Fixed(itemSize.toDp())) + row(GridTrackSize.Fixed(itemSize.toDp())) + flow = GridFlow.Column + }, + modifier = + Modifier.onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + // 4 items. + // Items 1, 2 fill Col 1 (Rows 1, 2) + // Items 3, 4 should create implicit Col 2 (Rows 1, 2) + repeat(4) { Box(Modifier.size(itemSize.toDp())) } + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Expected: 2 Rows (Explicit) x 2 Columns (1 Explicit + 1 Implicit) + // Implicit columns default to Auto -> itemSize (50) + assertEquals(IntSize(itemSize * 2, itemSize * 2), gridSize.value) + } + + @Test + fun testGrid_columnFlow_implicitRows() = + with(density) { + // Scenario: + // Flow = Column. + // Explicitly define 1 Column (so we know the width). + // Do NOT define any Rows. + // Item 1: (0,0) + // Item 2: Should stack below at (1,0) -> Creating Implicit Row 2 + // Item 3: Should stack below at (2,0) -> Creating Implicit Row 3 + + val size = 50.dp + val sizePx = size.roundToPx().toFloat() + val latch = CountDownLatch(3) + val pos = Array(3) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + flow = GridFlow.Column + // Define column width so layout isn't 0 width + column(GridTrackSize.Fixed(size)) + // Do NOT define rows. + // This allows the column to grow infinitely downwards. + } + ) { + // (0,0) + Box(Modifier.size(size).saveLayoutInfo(dummy, pos[0], latch)) + // (1, 0) -> Implicit Row 2 + Box(Modifier.size(size).saveLayoutInfo(dummy, pos[1], latch)) + // (2, 0) -> Implicit Row 3 + Box(Modifier.size(size).saveLayoutInfo(dummy, pos[2], latch)) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // All X should be 0 (Column 0) + // Y should increment by sizePx + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset(0f, sizePx), pos[1].value) + assertEquals(Offset(0f, sizePx * 2), pos[2].value) + } + + @Test + fun testGrid_mixedPlacement_skipsOccupiedCells() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(3) + val pos = Array(3) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + repeat(3) { column(GridTrackSize.Fixed(sizeDp)) } + row(GridTrackSize.Fixed(sizeDp)) + } + ) { + // 1. Explicit Item at (0, 1) (Middle Column) + Box(Modifier.gridItem(1, 2).size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) + + // 2. Auto Item 1 -> Should go to (0, 0) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) + + // 3. Auto Item 2 -> Should skip (0, 1) and go to (0, 2) + Box(Modifier.size(sizeDp).saveLayoutInfo(dummy, pos[2], latch)) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Explicit + assertEquals(Offset(size.toFloat(), 0f), pos[0].value) + // Auto 1 + assertEquals(Offset(0f, 0f), pos[1].value) + // Auto 2 + assertEquals(Offset((size * 2).toFloat(), 0f), pos[2].value) + } + + @Test + fun testGrid_explicitPlacement_allowsOverlaps() = + with(density) { + // Scenario: + // Two items explicitly placed in (1, 1). + // They should occupy the same space. The grid should not throw or shift them. + + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(2) + val pos = Array(2) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(sizeDp)) + row(GridTrackSize.Fixed(sizeDp)) + } + ) { + // Item 1 + Box(Modifier.gridItem(1, 1).size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) + // Item 2 (Same Cell) + Box(Modifier.gridItem(1, 1).size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset(0f, 0f), pos[1].value) + } + + @Test + fun testGrid_explicitPlacement_doesNotMoveAutoCursor() = + with(density) { + // Scenario: + // 3 Columns. + // Item 1: Auto (0,0). Cursor moves to (0,1). + // Item 2: Explicit far away (0, 2). Cursor should REMAIN at (0,1). + // Item 3: Auto. Should fill the gap at (0,1), NOT start after Item 2. + + val size = 50.dp + val latch = CountDownLatch(3) + val pos = Array(3) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + repeat(3) { column(GridTrackSize.Fixed(size)) } + row(GridTrackSize.Fixed(size)) + } + ) { + // 1. Auto -> (0,0) + Box(Modifier.size(size).saveLayoutInfo(dummy, pos[0], latch)) + // 2. Explicit -> (0,2). Should NOT move cursor. + Box(Modifier.gridItem(1, 3).size(size).saveLayoutInfo(dummy, pos[1], latch)) + // 3. Auto -> Should fill (0,1) + Box(Modifier.size(size).saveLayoutInfo(dummy, pos[2], latch)) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Calculate pixel size of a single track first + val sizePx = size.roundToPx().toFloat() + + // Auto 1 (0,0) + assertEquals(Offset(0f, 0f), pos[0].value) + // Explicit (Col 3 -> Index 2) + assertEquals(Offset(sizePx * 2, 0f), pos[1].value) + // Auto 2 (Col 2 -> Index 1) + assertEquals(Offset(sizePx, 0f), pos[2].value) + } + + @Test + fun testGrid_fixedRow_autoCol_skipsOccupied() = + with(density) { + // Scenario: + // 3 Columns. + // Item 1: Explicitly placed at (0, 0). + // Item 2: Fixed Row 0. Should skip (0,0) and go to (0,1). + // Item 3: Fixed Row 0. Should skip (0,0) and (0,1) and go to (0,2). + + val size = 50.dp + val latch = CountDownLatch(3) + val pos = Array(3) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + repeat(3) { column(GridTrackSize.Fixed(size)) } + row(GridTrackSize.Fixed(size)) + } + ) { + // 1. Occupy (0,0) explicitly + Box(Modifier.gridItem(1, 1).size(size).saveLayoutInfo(dummy, pos[0], latch)) + // 2. Request Row 1 (Auto Col). Should find Col 2. + Box(Modifier.gridItem(row = 1).size(size).saveLayoutInfo(dummy, pos[1], latch)) + // 3. Request Row 1 (Auto Col). Should find Col 3. + Box(Modifier.gridItem(row = 1).size(size).saveLayoutInfo(dummy, pos[2], latch)) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Calculate pixel size of a single track first + val sizePx = size.roundToPx().toFloat() + + // (0,0) + assertEquals(Offset(0f, 0f), pos[0].value) + // (50, 0) + assertEquals(Offset(sizePx, 0f), pos[1].value) + // (100, 0) -> Sum of two tracks + assertEquals(Offset(sizePx * 2, 0f), pos[2].value) + } + + @Test + fun testGrid_fixedCol_autoRow_skipsOccupied() = + with(density) { + // Scenario: + // 1 Column, 3 Rows. + // Item 1: Explicitly placed at (0, 0). + // Item 2: Fixed Col 0. Should skip (0,0) and go to (1,0). + + val size = 50.dp + val latch = CountDownLatch(2) + val pos = Array(2) { Ref() } + val dummy = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(size)) + repeat(3) { row(GridTrackSize.Fixed(size)) } + } + ) { + // 1. Occupy (0,0) explicitly + Box(Modifier.gridItem(1, 1).size(size).saveLayoutInfo(dummy, pos[0], latch)) + // 2. Request Col 1 (Auto Row). Should skip Row 1 and land in Row 2. + Box( + Modifier.gridItem(column = 1) + .size(size) + .saveLayoutInfo(dummy, pos[1], latch) + ) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Calculate pixel size of a single track first + val sizePx = size.roundToPx().toFloat() + + assertEquals(Offset(0f, 0f), pos[0].value) + assertEquals(Offset(0f, sizePx), pos[1].value) + } + + @Test + fun testGrid_mixedFlow_fixedRowInColumnFlow() = + with(density) { + // Scenario: + // Flow = Column. + // Item 1: Fixed Row 1. + // Since flow is column, the logic must look for the *first available column* in that + // fixed row. + + val size = 50.dp + val latch = CountDownLatch(1) + val pos = Ref() + val dummy = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(size)) + column(GridTrackSize.Fixed(size)) + row(GridTrackSize.Fixed(size)) + row(GridTrackSize.Fixed(size)) + flow = GridFlow.Column + } + ) { + // Occupy (1,0) - (Row 2, Col 1) + Box(Modifier.gridItem(2, 1).size(size)) + + // Request Row 2. + // Since (2,1) is occupied, it should find (2,2). + Box(Modifier.gridItem(row = 2).size(size).saveLayoutInfo(dummy, pos, latch)) + } + } + + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Calculate pixel size of a single track first + val sizePx = size.roundToPx().toFloat() + + // Should be at Row 2 (Index 1), Col 2 (Index 1) -> (50, 50) + assertEquals(Offset(sizePx, sizePx), pos.value) + } + + @Test + fun testGrid_negativeIndices_placeCorrectly() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + + val positionedLatch = CountDownLatch(4) + val childPosition = Array(4) { Ref() } + val dummySize = Array(4) { Ref() } + + show { + Grid( + config = { + repeat(3) { column(GridTrackSize.Fixed(sizeDp)) } + repeat(3) { row(GridTrackSize.Fixed(sizeDp)) } + } + ) { + // 1. Top-Left (1, 1) -> (0, 0) + Box( + Modifier.gridItem(1, 1) + .fillMaxSize() + .saveLayoutInfo(dummySize[0], childPosition[0], positionedLatch) + ) + // 2. Top-Right (1, -1) -> (100, 0) (Last Column) + Box( + Modifier.gridItem(1, -1) + .fillMaxSize() + .saveLayoutInfo(dummySize[1], childPosition[1], positionedLatch) + ) + // 3. Bottom-Left (-1, 1) -> (0, 100) (Last Row) + Box( + Modifier.gridItem(-1, 1) + .fillMaxSize() + .saveLayoutInfo(dummySize[2], childPosition[2], positionedLatch) + ) + // 4. Bottom-Right (-1, -1) -> (100, 100) (Last Row, Last Column) + Box( + Modifier.gridItem(-1, -1) + .fillMaxSize() + .saveLayoutInfo(dummySize[3], childPosition[3], positionedLatch) + ) + } + } + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + // 1. (0, 0) + assertEquals(Offset(0f, 0f), childPosition[0].value) + // 2. (100, 0) -> Col index 2 * 50 + assertEquals(Offset((size * 2).toFloat(), 0f), childPosition[1].value) + // 3. (0, 100) -> Row index 2 * 50 + assertEquals(Offset(0f, (size * 2).toFloat()), childPosition[2].value) + // 4. (100, 100) + assertEquals(Offset((size * 2).toFloat(), (size * 2).toFloat()), childPosition[3].value) + } + + @Test + fun testGrid_negativeIndex_refersToExplicitBounds() = + with(density) { + val size = 50 + val latch = CountDownLatch(1) + val pos = Ref() + + // Create a dummy ref instead of passing null + val dummy = Ref() + + show { + Grid( + config = { + repeat(2) { + column(GridTrackSize.Fixed(size.toDp())) + } // Explicit Cols: 0, 1 + row(GridTrackSize.Fixed(size.toDp())) + } + ) { + // Create an implicit 3rd column (Index 2) + Box(Modifier.gridItem(1, 3).size(size.toDp())) + + // Place item at column = -1. + // Should map to Explicit Col 1 (the 2nd column), NOT the implicit 3rd column. + Box( + Modifier.gridItem(column = -1) + .size(size.toDp()) + .saveLayoutInfo(dummy, pos, latch) + ) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Expect pos at 2nd column (Index 1) -> 50px + assertEquals(Offset(size.toFloat(), 0f), pos.value) + } + + @Test + fun testGrid_invalidNegativeIndices_fallbackToAuto() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(2) + val pos1 = Ref() + val pos2 = Ref() + val dummy = Ref() + + show { + Grid( + config = { + // 2x2 Grid + repeat(2) { column(GridTrackSize.Fixed(sizeDp)) } + repeat(2) { row(GridTrackSize.Fixed(sizeDp)) } + } + ) { + // Case 1: Valid Negative (-1 -> Index 1) + Box( + Modifier.gridItem(row = -1, column = -1) + .size(sizeDp) + .saveLayoutInfo(dummy, pos1, latch) + ) + + // Case 2: Invalid Negative (-5 -> Index -3 -> Invalid) + // Should be treated as "Unspecified" and auto-placed. + // Since (1,1) is empty (Item 1 is at 1,1 0-based), it should go to (0,0). + Box( + Modifier.gridItem(row = -5, column = -5) + .size(sizeDp) + .saveLayoutInfo(dummy, pos2, latch) + ) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Item 1 (Valid -1,-1): Bottom-Right (50, 50) assertEquals(Offset(size.toFloat(), size.toFloat()), pos1.value) // Item 2 (Invalid -5,-5): Auto-placed to first available slot (0,0) @@ -790,6 +1766,79 @@ class GridTest : LayoutTest() { assertEquals(size * 4, itemSize.value?.width) } + @Test + fun testGrid_spanning_intoImplicitTracks() = + with(density) { + val size = 50 + val latch = CountDownLatch(1) + val itemSize = Ref() + + show { + Grid( + config = { + column(GridTrackSize.Fixed(size.toDp())) // 1 Explicit Column + row(GridTrackSize.Fixed(size.toDp())) + } + ) { + // Place at Col 1, Span 2. + // Should cover Explicit Col 1 + Implicit Col 2. + // Implicit Col 2 should size to Auto. Since this item spans, + // and Auto tracks ignore spanning items for intrinsic sizing (as per your + // design), + // the implicit track might collapse to 0 OR resize if logic allows. + // *Correction*: Your logic adds `Auto` tracks. If no other item is in Col 2, + // it will be size 0. + // Let's add a non-spanning item in Col 2 to give it size. + + // Item A: Spans Col 1 and Col 2 + Box( + Modifier.gridItem(1, 1, columnSpan = 2) + .fillMaxSize() + .saveLayoutInfo(itemSize, Ref(), latch) + ) + + // Item B: Sits in Implicit Col 2 to force it to have size + Box(Modifier.gridItem(1, 2).size(size.toDp())) + } + } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Width = Col 1 (50) + Col 2 (50 from Item B) = 100 + assertEquals(size * 2, itemSize.value?.width) + } + + @Test + fun testGrid_itemSpanLargerThanExplicitGrid_doesNotLoop() = + with(density) { + val size = 50 + val sizeDp = size.toDp() + val latch = CountDownLatch(1) + val pos = Ref() + val dummy = Ref() + + show { + Grid( + config = { + // 2 Explicit Columns + column(GridTrackSize.Fixed(sizeDp)) + column(GridTrackSize.Fixed(sizeDp)) + flow = GridFlow.Row + } + ) { + // Item spans 3 columns (Exceeds explicit count of 2) + // Should be placed at (0,0) and create implicit tracks + Box( + Modifier.gridItem(columnSpan = 3) + .size(sizeDp) + .saveLayoutInfo(dummy, pos, latch) + ) + } + } + assertTrue("Timed out - likely infinite loop", latch.await(1, TimeUnit.SECONDS)) + + assertEquals(Offset(0f, 0f), pos.value) + } + @Test fun testGrid_respectsMinConstraints_expandsToFill() = with(density) { @@ -968,456 +2017,156 @@ class GridTest : LayoutTest() { } } - assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) - - // Width should be 10.dp (Size of the content), NOT 0. - // Height should be 50.dp (Fixed) - assertEquals(IntSize(10.dp.roundToPx(), 50.dp.roundToPx()), gridSize.value) - } - - @Test - fun testGrid_zeroSizeTrack_layoutCorrectly() = - with(density) { - val size = 50 - val latch = CountDownLatch(2) - val pos = Array(2) { Ref() } - val dummy = Ref() - - show { - Grid( - config = { - column(GridTrackSize.Fixed(size.toDp())) - column(GridTrackSize.Fixed(0.dp)) // Zero width column - column(GridTrackSize.Fixed(size.toDp())) - row(GridTrackSize.Fixed(size.toDp())) - } - ) { - // Item 1: Col 1 - Box( - Modifier.gridItem(1, 1) - .size(size.toDp()) - .saveLayoutInfo(dummy, pos[0], latch) - ) - // Item 2: Col 3 (Skipping Col 2 which is 0 width) - Box( - Modifier.gridItem(1, 3) - .size(size.toDp()) - .saveLayoutInfo(dummy, pos[1], latch) - ) - } - } - assertTrue(latch.await(1, TimeUnit.SECONDS)) - - // Item 1 at 0 - assertEquals(Offset(0f, 0f), pos[0].value) - // Item 2 at 50 + 0 = 50 - assertEquals(Offset(size.toFloat(), 0f), pos[1].value) - } - - @Test - fun testGrid_zeroSizeChildren() { - val trackSize = 50 - val latch = CountDownLatch(1) - val gridSize = Ref() - - show { - Grid( - config = { - column(GridTrackSize.Fixed(trackSize.toDp())) - row(GridTrackSize.Fixed(trackSize.toDp())) - }, - modifier = - Modifier.onGloballyPositioned { - gridSize.value = it.size - latch.countDown() - }, - ) { - // Place a zero-sized item. - // It should still occupy the logical cell (1,1), but draw nothing. - // The Grid should still size itself to the Fixed tracks (50x50). - Box(Modifier.gridItem(1, 1).size(0.dp)) - } - } - - assertTrue(latch.await(1, TimeUnit.SECONDS)) - assertEquals(IntSize(trackSize, trackSize), gridSize.value) - } - - @Test - fun testGrid_itemFillsCell_whenRequested() { - val trackSize = 100 - val latch = CountDownLatch(1) - val childSize = Ref() - - show { - Grid( - config = { - column(GridTrackSize.Fixed(trackSize.toDp())) - row(GridTrackSize.Fixed(trackSize.toDp())) - } - ) { - // Item has no intrinsic size, but requests fillMaxSize(). - // It should fill the definition of the track (100x100). - Box(Modifier.gridItem(1, 1).fillMaxSize().saveLayoutInfo(childSize, Ref(), latch)) - } - } - - assertTrue(latch.await(1, TimeUnit.SECONDS)) - assertEquals(IntSize(trackSize, trackSize), childSize.value) - } - - @Test - fun testGrid_gaps() = - with(density) { - val size = 50 - val gap = 10 - val gapDp = gap.toDp() - val sizeDp = size.toDp() - - val positionedLatch = CountDownLatch(2) - val childPosition = Array(2) { Ref() } - // Use explicit dummy refs instead of passing null to avoid overload ambiguity - val dummySize = Array(2) { Ref() } - - show { - Grid( - config = { - column(GridTrackSize.Fixed(sizeDp)) - column(GridTrackSize.Fixed(sizeDp)) - row(GridTrackSize.Fixed(sizeDp)) - gap(gapDp) - } - ) { - // Item 1: (0, 0) - Box( - Modifier.gridItem(1, 1) - .fillMaxSize() - .saveLayoutInfo(dummySize[0], childPosition[0], positionedLatch) - ) - // Item 2: (50 + 10, 0) = (60, 0) - Box( - Modifier.gridItem(1, 2) - .fillMaxSize() - .saveLayoutInfo(dummySize[1], childPosition[1], positionedLatch) - ) - } - } - assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) - - assertEquals(Offset(0f, 0f), childPosition[0].value) - assertEquals(Offset((size + gap).toFloat(), 0f), childPosition[1].value) - } - - @Test - fun testGrid_flexWithGaps() = - with(density) { - val size = 50 - val gap = 10 - // Available space for tracks: 50 - 10 = 40. - // 1.fr + 1.fr = 2 parts. 40 / 2 = 20 per track. - val expectedColWidth = (size - gap) / 2 - - val positionedLatch = CountDownLatch(2) - val childSize = Array(2) { Ref() } - val childPosition = Array(2) { Ref() } - - show { - Grid( - config = { - column(GridTrackSize.Flex(1.fr)) - column(GridTrackSize.Flex(1.fr)) - row(GridTrackSize.Fixed(size.toDp())) - gap(gap.toDp()) - }, - modifier = Modifier.requiredSize(size.toDp()), - ) { - Box( - Modifier.gridItem(1, 1) - .fillMaxSize() - .saveLayoutInfo(childSize[0], childPosition[0], positionedLatch) - ) - Box( - Modifier.gridItem(1, 2) - .fillMaxSize() - .saveLayoutInfo(childSize[1], childPosition[1], positionedLatch) - ) - } - } - assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) - assertEquals(IntSize(expectedColWidth, size), childSize[0].value) - assertEquals(IntSize(expectedColWidth, size), childSize[1].value) - assertEquals(Offset(0f, 0f), childPosition[0].value) - assertEquals(Offset((expectedColWidth + gap).toFloat(), 0f), childPosition[1].value) - } - - @Test - fun testGrid_spanningWithGaps() { - val size = 50 - val gap = 10 - // Spanning 2 columns: size + gap + size = 50 + 10 + 50 = 110 - val expectedWidth = size * 2 + gap - - val positionedLatch = CountDownLatch(1) - val childSize = Ref() - val dummyPos = Ref() - - show { - Grid( - config = { - repeat(3) { column(GridTrackSize.Fixed(size.toDp())) } - row(GridTrackSize.Fixed(size.toDp())) - gap(gap.toDp()) - } - ) { - Box( - Modifier.gridItem(row = 1, column = 1, columnSpan = 2) - .fillMaxSize() - .saveLayoutInfo(childSize, dummyPos, positionedLatch) - ) - } - } - assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) - assertEquals(IntSize(expectedWidth, size), childSize.value) - } - - @Test - fun testGrid_gapPrecedence_specificOverridesGeneric() = - with(density) { - // Scenario: - // gap(10) sets both. - // rowGap(20) overrides row gap. - // columnGap(5) overrides column gap. - // Result: RowGap = 20, ColumnGap = 5. - - val size = 50 - val baseGap = 10 - val rowGapOverride = 20 - val colGapOverride = 5 - - val sizeDp = size.toDp() - val latch = CountDownLatch(3) - val pos = Array(3) { Ref() } - val dummy = Ref() - - show { - Grid( - config = { - repeat(2) { column(GridTrackSize.Fixed(sizeDp)) } - repeat(2) { row(GridTrackSize.Fixed(sizeDp)) } - - gap(baseGap.toDp()) // Sets both to 10 - rowGap(rowGapOverride.toDp()) // Overrides row to 20 - columnGap(colGapOverride.toDp()) // Overrides col to 5 - } - ) { - // (0,0) - Box(Modifier.gridItem(1, 1).size(sizeDp).saveLayoutInfo(dummy, pos[0], latch)) - // (0,1) -> X should be Size + ColGap(5) - Box(Modifier.gridItem(1, 2).size(sizeDp).saveLayoutInfo(dummy, pos[1], latch)) - // (1,0) -> Y should be Size + RowGap(20) - Box(Modifier.gridItem(2, 1).size(sizeDp).saveLayoutInfo(dummy, pos[2], latch)) - } - } - - assertTrue(latch.await(1, TimeUnit.SECONDS)) - - assertEquals(Offset(0f, 0f), pos[0].value) - assertEquals(Offset((size + colGapOverride).toFloat(), 0f), pos[1].value) - assertEquals(Offset(0f, (size + rowGapOverride).toFloat()), pos[2].value) + assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) + + // Width should be 10.dp (Size of the content), NOT 0. + // Height should be 50.dp (Fixed) + assertEquals(IntSize(10.dp.roundToPx(), 50.dp.roundToPx()), gridSize.value) } @Test - fun testGrid_alignment() = + fun testGrid_flexColumn_inInfiniteConstraints_withGap_doesNotExpand() = with(density) { - val cellSize = 100 - val itemSize = 40 - // Center: (100 - 40) / 2 = 30 - val expectedOffset = 30f - val cellSizeDp = cellSize.toDp() - val itemSizeDp = itemSize.toDp() + // Scenario: Grid is inside a Row + horizontalScroll (Infinite Width). + // It has a Flex column and a GAP. + // + // Bug Trigger: + // 1. Available Width = Infinity. + // 2. Gap = 10px. + // 3. Calculation: availableTrackSpace = Infinity - 10 = 2,147,483,637. + // 4. Check: (availableTrackSpace == Infinity) is FALSE. + // 5. Result: Logic thinks it has 2 billion pixels of space to distribute. + // 6. Flex track expands to ~2 billion pixels. + + val gap = 10.dp + val itemSize = 50.dp + val expectedWidth = itemSize.roundToPx() // Should shrink to fit content - val positionedLatch = CountDownLatch(1) - val childPosition = Ref() - val dummySize = Ref() + val latch = CountDownLatch(1) + val gridSize = Ref() show { - Grid( - config = { - column(GridTrackSize.Fixed(cellSizeDp)) - row(GridTrackSize.Fixed(cellSizeDp)) + // Parent provides Infinite Width constraint + Row(Modifier.horizontalScroll(rememberScrollState())) { + Grid( + config = { + column(GridTrackSize.Flex(1.fr)) // Should behave like MinContent/Auto + column( + GridTrackSize.Fixed(0.dp) + ) // Dummy column to ensure gap is applied + row(GridTrackSize.Fixed(50.dp)) + columnGap(gap) + }, + modifier = + Modifier.onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + // Item in Flex column + Box(Modifier.gridItem(1, 1).size(itemSize)) } - ) { - // Item smaller than cell, aligned center - Box( - Modifier.gridItem(1, 1, alignment = Alignment.Center) - .size(itemSizeDp) - .saveLayoutInfo(dummySize, childPosition, positionedLatch) - ) } } - assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) - assertEquals(Offset(expectedOffset, expectedOffset), childPosition.value) + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + assertEquals( + "Flex column in infinite constraints should fallback to min-content size", + expectedWidth + gap.roundToPx(), // 50 (Item) + 10 (Gap) + 0 (Col 2) + gridSize.value?.width, + ) } @Test - fun testGrid_alignment_spanning() = + fun testGrid_zeroSizeTrack_layoutCorrectly() = with(density) { - val colSize = 50 - val rowSize = 60 - val gap = 10 - val itemSize = 40 - - // Calculate total area dimensions including the gap - val spannedWidth = (colSize * 2) + gap - val spannedHeight = (rowSize * 2) + gap - - // Alignment.BottomEnd calculation: - // x = ContainerWidth - ItemWidth - // y = ContainerHeight - ItemHeight - val expectedX = (spannedWidth - itemSize).toFloat() - val expectedY = (spannedHeight - itemSize).toFloat() - - val positionedLatch = CountDownLatch(1) - val childPosition = Ref() - val dummySize = Ref() + val size = 50 + val latch = CountDownLatch(2) + val pos = Array(2) { Ref() } + val dummy = Ref() show { Grid( config = { - repeat(2) { column(GridTrackSize.Fixed(colSize.toDp())) } - repeat(2) { row(GridTrackSize.Fixed(rowSize.toDp())) } - gap(gap.toDp()) + column(GridTrackSize.Fixed(size.toDp())) + column(GridTrackSize.Fixed(0.dp)) // Zero width column + column(GridTrackSize.Fixed(size.toDp())) + row(GridTrackSize.Fixed(size.toDp())) } ) { + // Item 1: Col 1 Box( - Modifier.gridItem( - 1, - 1, - rowSpan = 2, - columnSpan = 2, - alignment = Alignment.BottomEnd, - ) - .size(itemSize.toDp()) - .saveLayoutInfo(dummySize, childPosition, positionedLatch) + Modifier.gridItem(1, 1) + .size(size.toDp()) + .saveLayoutInfo(dummy, pos[0], latch) + ) + // Item 2: Col 3 (Skipping Col 2 which is 0 width) + Box( + Modifier.gridItem(1, 3) + .size(size.toDp()) + .saveLayoutInfo(dummy, pos[1], latch) ) } } - assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) - assertEquals(Offset(expectedX, expectedY), childPosition.value) + assertTrue(latch.await(1, TimeUnit.SECONDS)) + + // Item 1 at 0 + assertEquals(Offset(0f, 0f), pos[0].value) + // Item 2 at 50 + 0 = 50 + assertEquals(Offset(size.toFloat(), 0f), pos[1].value) } @Test - fun testGrid_alignment_rtl() = - with(density) { - val cellSize = 100 - val itemSize = 40 - val cellSizeDp = cellSize.toDp() - val itemSizeDp = itemSize.toDp() - - val positionedLatch = CountDownLatch(2) - val startAlignedPos = Ref() - val absLeftAlignedPos = Ref() - - show { - // Force RTL layout direction - androidx.compose.runtime.CompositionLocalProvider( - androidx.compose.ui.platform.LocalLayoutDirection provides - androidx.compose.ui.unit.LayoutDirection.Rtl - ) { - Grid( - config = { - column(GridTrackSize.Fixed(cellSizeDp)) - row(GridTrackSize.Fixed(cellSizeDp)) - } - ) { - // 1. Alignment.TopStart (Relative 2D Alignment) - // In RTL, "Start" is visually on the RIGHT. - // Cell Width 100. Item 40. - // Expect visual position: 100 - 40 = 60px from visual Left. - Box( - Modifier.gridItem(1, 1, alignment = Alignment.TopStart) - .size(itemSizeDp) - .saveLayoutInfo(Ref(), startAlignedPos, positionedLatch) - ) + fun testGrid_zeroSizeChildren() { + val trackSize = 50 + val latch = CountDownLatch(1) + val gridSize = Ref() - // 2. AbsoluteAlignment.TopLeft (Absolute 2D Alignment) - // "Left" is always visually Left, regardless of layout direction. - // Expect visual position: 0px from visual Left. - Box( - // Use TopLeft (2D) instead of Left (1D) to satisfy the Alignment - // type requirement - Modifier.gridItem( - 1, - 1, - alignment = androidx.compose.ui.AbsoluteAlignment.TopLeft, - ) - .size(itemSizeDp) - .saveLayoutInfo(Ref(), absLeftAlignedPos, positionedLatch) - ) - } - } + show { + Grid( + config = { + column(GridTrackSize.Fixed(trackSize.toDp())) + row(GridTrackSize.Fixed(trackSize.toDp())) + }, + modifier = + Modifier.onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + // Place a zero-sized item. + // It should still occupy the logical cell (1,1), but draw nothing. + // The Grid should still size itself to the Fixed tracks (50x50). + Box(Modifier.gridItem(1, 1).size(0.dp)) } - - assertTrue(positionedLatch.await(1, TimeUnit.SECONDS)) - - // 1. Start (Right side of container) - assertEquals( - "Alignment.TopStart in RTL should be visually on the Right (60px from left)", - Offset(60f, 0f), - startAlignedPos.value, - ) - - // 2. Absolute Left (Left side of container) - assertEquals( - "AbsoluteAlignment.TopLeft in RTL should visually remain on the Left (0px)", - Offset(0f, 0f), - absLeftAlignedPos.value, - ) } + assertTrue(latch.await(1, TimeUnit.SECONDS)) + assertEquals(IntSize(trackSize, trackSize), gridSize.value) + } + @Test - fun testGrid_contentBasedSizing() { - val childSize = Ref() + fun testGrid_itemFillsCell_whenRequested() { + val trackSize = 100 val latch = CountDownLatch(1) - val dummyPosition = Ref() + val childSize = Ref() show { Grid( config = { - // Col 1: MinContent (should match min intrinsic width) - column(GridTrackSize.MinContent) - // Col 2: MaxContent (should match max intrinsic width) - column(GridTrackSize.MaxContent) - row(GridTrackSize.Fixed(50.dp)) + column(GridTrackSize.Fixed(trackSize.toDp())) + row(GridTrackSize.Fixed(trackSize.toDp())) } ) { - // Item 1: Min=50, Max=100 - IntrinsicItem( - minWidth = 50, - minIntrinsicWidth = 50, - maxIntrinsicWidth = 100, - modifier = Modifier.gridItem(1, 1).fillMaxSize(), - ) - - // Item 2: Min=50, Max=100 - IntrinsicItem( - minWidth = 50, - minIntrinsicWidth = 50, - maxIntrinsicWidth = 100, - modifier = - Modifier.gridItem(1, 2) - .fillMaxSize() - .saveLayoutInfo(childSize, dummyPosition, latch), - ) + // Item has no intrinsic size, but requests fillMaxSize(). + // It should fill the definition of the track (100x100). + Box(Modifier.gridItem(1, 1).fillMaxSize().saveLayoutInfo(childSize, Ref(), latch)) } } - assertTrue(latch.await(1, TimeUnit.SECONDS)) - // Col 1 should be 50 (min) - // Col 2 should be 100 (max) - // Item 2 in Col 2 should have width 100 - assertEquals(100, childSize.value?.width) + assertTrue(latch.await(1, TimeUnit.SECONDS)) + assertEquals(IntSize(trackSize, trackSize), childSize.value) } @Test @@ -1464,6 +2213,45 @@ class GridTest : LayoutTest() { assertEquals(IntSize(50, 100), innerItemSize.value) } + @Test + fun testGrid_stressTest_manyItems() = + with(density) { + val itemSize = 10 + val itemSizeDp = itemSize.toDp() + val itemCount = 100 + val cols = 10 + + // 100 items / 10 cols = 10 rows. + // Total Height = 10 rows * 10px = 100px. + val expectedHeight = 100 + + val latch = CountDownLatch(1) + val gridSize = Ref() + + show { + Grid( + config = { + // 10 Fixed columns + repeat(cols) { column(GridTrackSize.Fixed(itemSizeDp)) } + // Implicit rows + flow = GridFlow.Row + }, + modifier = + Modifier.onGloballyPositioned { + gridSize.value = it.size + latch.countDown() + }, + ) { + repeat(itemCount) { Box(Modifier.size(itemSizeDp)) } + } + } + + assertTrue(latch.await(3, TimeUnit.SECONDS)) + + assertEquals(10 * itemSize, gridSize.value?.width) + assertEquals(expectedHeight, gridSize.value?.height) + } + @Test(expected = IllegalArgumentException::class) fun testGrid_invalidIndices_throws() { show { diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt index d15812b7b9119..856fe89ebf8e6 100644 --- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt +++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt @@ -18,6 +18,7 @@ package androidx.compose.foundation.layout import androidx.annotation.FloatRange import androidx.collection.LongList +import androidx.collection.MutableIntSet import androidx.collection.MutableObjectList import androidx.collection.mutableLongListOf import androidx.compose.foundation.layout.GridScope.Companion.GridIndexUnspecified @@ -590,7 +591,7 @@ internal class GridMeasurePolicy( // 1. Run Configuration DSL val gridConfig = GridConfigurationScopeImpl(this).apply(configState.value) - // 2. Resolve Grid Item Indices (Resolve explicit) (Auto placement added in followup cl) + // 2. Resolve Grid Item Indices (Resolve explicit and Auto placement) // This calculates the concrete index (row, col) for every item and determines total grid // size. val resolvedGridItemsResult = @@ -782,6 +783,14 @@ private class GridTrackSizes( * **Algorithm Overview:** * 1. **Explicit Placement:** Items with both `row` and `column` manually specified are placed * first. They anchor the grid and do not move. + * 2. **Auto-Placement Cursor:** A "cursor" (current row/column pointer) tracks the next available + * position. + * 3. **Filling Gaps:** The algorithm iterates through the remaining items. For each item: + * - It advances the cursor to the first slot that can accommodate the item's span without + * overlapping existing items. + * - It respects the [flow] direction (Row-major vs Column-major). + * - It creates "Implicit Tracks" (expanding the grid bounds) if an item is placed outside the + * currently defined area. * * @param measurables The raw list of children to place. * @param columnSpecs The explicit column definitions (used to determine wrapping points). @@ -789,8 +798,6 @@ private class GridTrackSizes( * @param flow The direction ([GridFlow.Row] or [GridFlow.Column]) to fill the grid. * @return A [ResolvedGridItemIndicesResult] containing the final positions and the *total* grid * dimensions (Explicit + Implicit). - * - * TODO: handle auto placement */ private fun resolveGridItemIndices( measurables: List, @@ -799,9 +806,51 @@ private fun resolveGridItemIndices( flow: GridFlow, ): ResolvedGridItemIndicesResult { val gridItems = MutableObjectList(measurables.size) + + // Key = (row shl 16) | (column & 0xFFFF) + // Supports up to 65,535 rows/cols (well within MaxGridIndex). + val occupiedCells = MutableIntSet() + val explicitColCount = columnSpecs.size val explicitRowCount = rowSpecs.size + // Track the effective size of the grid (starts at explicit size, expands if items are placed + // outside) + var maxRow = explicitRowCount + var maxCol = explicitColCount + + // Pack into Int (Row in high 16 bits, Col in low 16 bits) + // Supports up to 65,535 rows/cols. + fun packCoordinate(row: Int, column: Int): Int = (row shl 16) or (column and 0xFFFF) + + // Checks if the target area (defined by start position and span) overlaps with any existing + // item. + fun isAreaOccupied(startRow: Int, startCol: Int, rowSpan: Int, colSpan: Int): Boolean { + // Fast-path: Check boundary limits first + if (startRow + rowSpan > MaxGridIndex || startCol + colSpan > MaxGridIndex) return true + for (r in startRow until startRow + rowSpan) { + for (c in startCol until startCol + colSpan) { + if (occupiedCells.contains(packCoordinate(r, c))) return true + } + } + return false + } + + // Marks the cells in the area as occupied. + fun markAreaOccupied(startRow: Int, startCol: Int, rowSpan: Int, colSpan: Int) { + for (r in startRow until startRow + rowSpan) { + for (c in startCol until startCol + colSpan) { + occupiedCells.add(packCoordinate(r, c)) + } + } + } + + // The "Cursor" tracks the position of the last auto-placed item. + // Subsequent auto-placed items attempt to start searching from here to avoid re-scanning the + // whole grid. + var autoPlacementCursorRow = 0 + var autoPlacementCursorCol = 0 + measurables.fastForEach { measurable -> val data = measurable.parentData as? GridItemNode val rowSpan = data?.rowSpan ?: 1 @@ -823,13 +872,103 @@ private fun resolveGridItemIndices( finalRow = requestedRow finalCol = requestedCol } - // TODO Handle Fixed Row, Or Fixed Column or Fully Auto added in followup cl + // 2. Fixed Row (Search for Column) + else if (requestedRow != -1) { + // Search for the first available column in the specified row. + finalRow = requestedRow + var candidateCol = 0 + + // If flowing by Row, and we are on the cursor's row, start searching from cursor + if (flow == GridFlow.Row && requestedRow == autoPlacementCursorRow) { + candidateCol = autoPlacementCursorCol + } + while (candidateCol < MaxGridIndex) { + if (!isAreaOccupied(requestedRow, candidateCol, rowSpan, colSpan)) { + finalCol = candidateCol + break + } + candidateCol++ + } + } + // 3. Fixed Column (Search for Row) + else if (requestedCol != -1) { + // Search for the first available row in the specified column. + finalCol = requestedCol + var candidateRow = 0 + // If flowing by Column, and we are on the cursor's col, start searching from cursor + if (flow == GridFlow.Column && requestedCol == autoPlacementCursorCol) { + candidateRow = autoPlacementCursorRow + } + while (candidateRow < MaxGridIndex) { + if (!isAreaOccupied(candidateRow, requestedCol, rowSpan, colSpan)) { + finalRow = candidateRow + break + } + candidateRow++ + } + } + // 4. Fully Auto (Search for Slot) + else { + // Start searching from the current cursor position. + var candidateRow = autoPlacementCursorRow + var candidateCol = autoPlacementCursorCol + + while (candidateRow < MaxGridIndex && candidateCol < MaxGridIndex) { + // Wrapping Logic + // If the item doesn't fit in the current track (explicit bounds), wrap to next. + if (flow == GridFlow.Row) { + // If we have explicit columns and exceed them... + if (explicitColCount > 0 && candidateCol + colSpan > explicitColCount) { + // If we are NOT at start, wrap. + // If we ARE at start (0) and still don't fit, we must overflow (create + // implicit track). + if (candidateCol > 0) { + candidateCol = 0 + candidateRow++ + continue // Re-evaluate wrapping at new position + } + } + } else { // GridFlow.Column + if (explicitRowCount > 0 && candidateRow + rowSpan > explicitRowCount) { + if (candidateRow > 0) { + candidateRow = 0 + candidateCol++ + continue + } + } + } + + if (!isAreaOccupied(candidateRow, candidateCol, rowSpan, colSpan)) { + finalRow = candidateRow + finalCol = candidateCol + break + } + + // Increment + if (flow == GridFlow.Row) { + candidateCol++ + // If we drift too far right without wrapping (infinite grid), force wrap safety + if (candidateCol > MaxGridIndex) { + candidateCol = 0 + candidateRow++ + } + } else { + candidateRow++ + if (candidateRow > MaxGridIndex) { + candidateRow = 0 + candidateCol++ + } + } + } + } // If auto-placement failed to find a spot (e.g. MaxGridIndex reached), // we default to 0,0 to avoid crashing, though visual overlap will occur. val placementRow = max(0, finalRow) val placementCol = max(0, finalCol) + markAreaOccupied(placementRow, placementCol, rowSpan, colSpan) + // Populate the mutable GridItem gridItems.add( GridItem( @@ -841,9 +980,26 @@ private fun resolveGridItemIndices( alignment = data?.alignment ?: Alignment.TopStart, ) ) + + // Expand total grid bounds if necessary + maxRow = max(maxRow, placementRow + rowSpan) + maxCol = max(maxCol, placementCol + colSpan) + + // Update Cursor (Only for non-explicit placements) + // Only update cursor if the item was NOT fully explicit. + // Explicit items are "out of flow" and shouldn't drag the cursor with them. + if (requestedRow == -1 || requestedCol == -1) { + if (flow == GridFlow.Row) { + autoPlacementCursorRow = placementRow + autoPlacementCursorCol = placementCol + colSpan + } else { + autoPlacementCursorRow = placementRow + rowSpan + autoPlacementCursorCol = placementCol + } + } } - return ResolvedGridItemIndicesResult(gridItems, IntSize(explicitColCount, explicitRowCount)) + return ResolvedGridItemIndicesResult(gridItems, IntSize(maxCol, maxRow)) } /** From e6b7e928ac53c3d537ca47ca2eb47707a7911ff4 Mon Sep 17 00:00:00 2001 From: Prashant Date: Wed, 14 Jan 2026 03:28:41 +0000 Subject: [PATCH 09/19] Annotate Grid layout with ExperimentalGridApi annotation Bug: 475521540 Relnote: "All Grid related APIs are currently experimental and require opt-in with @OptIn(ExperimentalGridApi::class). We are actively seeking feedback on this new layout!" Test: NA Change-Id: I20d0df09dba18c49111c5f9234a09a11abfcd29b --- .../foundation-layout/api/current.txt | 53 +++++++++-------- .../api/restricted_current.txt | 57 ++++++++++--------- .../foundation-layout/bcv/native/current.txt | 4 ++ .../foundation/layout/demos/GridDemo.kt | 4 ++ .../compose/foundation/layout/GridTest.kt | 2 + .../foundation/layout/ExperimentalGridApi.kt | 23 ++++++++ .../compose/foundation/layout/Grid.kt | 24 +++++++- 7 files changed, 112 insertions(+), 55 deletions(-) create mode 100644 compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ExperimentalGridApi.kt diff --git a/compose/foundation/foundation-layout/api/current.txt b/compose/foundation/foundation-layout/api/current.txt index 5382a9981ee2a..2ec9ce764e817 100644 --- a/compose/foundation/foundation-layout/api/current.txt +++ b/compose/foundation/foundation-layout/api/current.txt @@ -204,6 +204,9 @@ package androidx.compose.foundation.layout { property @Deprecated public abstract androidx.compose.ui.unit.Dp maxWidthInLine; } + @SuppressCompatibility @kotlin.RequiresOptIn(message="This foundation layout API is experimental and is likely to change or be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalGridApi { + } + @SuppressCompatibility @kotlin.RequiresOptIn(message="The API of this layout is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalLayoutApi { } @@ -275,7 +278,7 @@ package androidx.compose.foundation.layout { method @BytecodeOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public static androidx.compose.ui.Modifier! fillMaxRowHeight$default(androidx.compose.foundation.layout.FlowRowScope!, androidx.compose.ui.Modifier!, float, int, Object!); } - @kotlin.jvm.JvmInline public final value class Fr { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @kotlin.jvm.JvmInline public final value class Fr { ctor @KotlinOnly public Fr(float value); method @BytecodeOnly public static androidx.compose.foundation.layout.Fr! box-impl(float); method @BytecodeOnly public static float constructor-impl(float); @@ -284,7 +287,7 @@ package androidx.compose.foundation.layout { property public float value; } - @androidx.compose.foundation.layout.LayoutScopeMarker public interface GridConfigurationScope extends androidx.compose.ui.unit.Density { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.foundation.layout.LayoutScopeMarker public interface GridConfigurationScope extends androidx.compose.ui.unit.Density { method @KotlinOnly public void column(androidx.compose.foundation.layout.Fr weight); method @KotlinOnly public void column(androidx.compose.foundation.layout.GridTrackSize size); method @KotlinOnly public void column(androidx.compose.ui.unit.Dp size); @@ -313,55 +316,55 @@ package androidx.compose.foundation.layout { method @BytecodeOnly public void rowGap-0680j_4(float); method @BytecodeOnly public void setFlow-4t4_IgM(int); property public abstract androidx.compose.foundation.layout.GridFlow flow; - property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr int.fr; - property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr float.fr; - property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr double.fr; + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr int.fr; + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr float.fr; + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr double.fr; } - @kotlin.jvm.JvmInline public final value class GridFlow { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @kotlin.jvm.JvmInline public final value class GridFlow { method @BytecodeOnly public static androidx.compose.foundation.layout.GridFlow! box-impl(int); method @BytecodeOnly public int unbox-impl(); field public static final androidx.compose.foundation.layout.GridFlow.Companion Companion; } - public static final class GridFlow.Companion { - method @BytecodeOnly public int getColumn-ITJdzs4(); - method @BytecodeOnly public int getRow-ITJdzs4(); - property public inline androidx.compose.foundation.layout.GridFlow Column; - property public inline androidx.compose.foundation.layout.GridFlow Row; + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static final class GridFlow.Companion { + method @BytecodeOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public int getColumn-ITJdzs4(); + method @BytecodeOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public int getRow-ITJdzs4(); + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public inline androidx.compose.foundation.layout.GridFlow Column; + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public inline androidx.compose.foundation.layout.GridFlow Row; } - public final class GridKt { - method @BytecodeOnly @androidx.compose.runtime.Composable public static void Grid(kotlin.jvm.functions.Function1, androidx.compose.ui.Modifier?, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); - method @KotlinOnly @androidx.compose.runtime.Composable public static inline void Grid(kotlin.jvm.functions.Function1 config, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1 content); - method public static void columns(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); - method public static void rows(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); + @SuppressCompatibility public final class GridKt { + method @BytecodeOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Composable public static void Grid(kotlin.jvm.functions.Function1, androidx.compose.ui.Modifier?, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); + method @KotlinOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Composable public static inline void Grid(kotlin.jvm.functions.Function1 config, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1 content); + method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static void columns(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); + method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static void rows(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); } - @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable @kotlin.jvm.JvmDefaultWithCompatibility public interface GridScope { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable @kotlin.jvm.JvmDefaultWithCompatibility public interface GridScope { method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, optional int row, optional int column, optional int rowSpan, optional int columnSpan, optional androidx.compose.ui.Alignment alignment); method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, kotlin.ranges.IntRange rows, kotlin.ranges.IntRange columns, optional androidx.compose.ui.Alignment alignment); method @BytecodeOnly @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier! gridItem$default(androidx.compose.foundation.layout.GridScope!, androidx.compose.ui.Modifier!, int, int, int, int, androidx.compose.ui.Alignment!, int, Object!); method @BytecodeOnly @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier! gridItem$default(androidx.compose.foundation.layout.GridScope!, androidx.compose.ui.Modifier!, kotlin.ranges.IntRange!, kotlin.ranges.IntRange!, androidx.compose.ui.Alignment!, int, Object!); field public static final androidx.compose.foundation.layout.GridScope.Companion Companion; - field public static final int GridIndexUnspecified = 0; // 0x0 - field public static final int MaxGridIndex = 1000; // 0x3e8 + field @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static final int GridIndexUnspecified = 0; // 0x0 + field @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static final int MaxGridIndex = 1000; // 0x3e8 } - public static final class GridScope.Companion { - property public static int GridIndexUnspecified; - property public static int MaxGridIndex; + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static final class GridScope.Companion { + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static int GridIndexUnspecified; + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static int MaxGridIndex; field public static final int GridIndexUnspecified = 0; // 0x0 field public static final int MaxGridIndex = 1000; // 0x3e8 } - @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class GridTrackSize implements androidx.compose.foundation.layout.GridTrackSpec { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class GridTrackSize implements androidx.compose.foundation.layout.GridTrackSpec { method @BytecodeOnly public static androidx.compose.foundation.layout.GridTrackSize! box-impl(long); method @BytecodeOnly public long unbox-impl(); field public static final androidx.compose.foundation.layout.GridTrackSize.Companion Companion; } - public static final class GridTrackSize.Companion { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static final class GridTrackSize.Companion { method @KotlinOnly @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Fixed(androidx.compose.ui.unit.Dp size); method @BytecodeOnly @androidx.compose.runtime.Stable public long Fixed-psSkOvk(float); method @KotlinOnly @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Flex(@FloatRange(from=0.0) androidx.compose.foundation.layout.Fr weight); @@ -376,7 +379,7 @@ package androidx.compose.foundation.layout { property @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize MinContent; } - public sealed exhaustive interface GridTrackSpec { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public sealed exhaustive interface GridTrackSpec { } public final class IntrinsicKt { diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt index b7ba6fb8ff5e6..e72e674f85b17 100644 --- a/compose/foundation/foundation-layout/api/restricted_current.txt +++ b/compose/foundation/foundation-layout/api/restricted_current.txt @@ -223,6 +223,9 @@ package androidx.compose.foundation.layout { property @Deprecated public abstract androidx.compose.ui.unit.Dp maxWidthInLine; } + @SuppressCompatibility @kotlin.RequiresOptIn(message="This foundation layout API is experimental and is likely to change or be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalGridApi { + } + @SuppressCompatibility @kotlin.RequiresOptIn(message="The API of this layout is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalLayoutApi { } @@ -298,7 +301,7 @@ package androidx.compose.foundation.layout { method @BytecodeOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public static androidx.compose.ui.Modifier! fillMaxRowHeight$default(androidx.compose.foundation.layout.FlowRowScope!, androidx.compose.ui.Modifier!, float, int, Object!); } - @kotlin.jvm.JvmInline public final value class Fr { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @kotlin.jvm.JvmInline public final value class Fr { ctor @KotlinOnly public Fr(float value); method @BytecodeOnly public static androidx.compose.foundation.layout.Fr! box-impl(float); method @BytecodeOnly public static float constructor-impl(float); @@ -307,7 +310,7 @@ package androidx.compose.foundation.layout { property public float value; } - @androidx.compose.foundation.layout.LayoutScopeMarker public interface GridConfigurationScope extends androidx.compose.ui.unit.Density { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.foundation.layout.LayoutScopeMarker public interface GridConfigurationScope extends androidx.compose.ui.unit.Density { method @KotlinOnly public void column(androidx.compose.foundation.layout.Fr weight); method @KotlinOnly public void column(androidx.compose.foundation.layout.GridTrackSize size); method @KotlinOnly public void column(androidx.compose.ui.unit.Dp size); @@ -336,12 +339,12 @@ package androidx.compose.foundation.layout { method @BytecodeOnly public void rowGap-0680j_4(float); method @BytecodeOnly public void setFlow-4t4_IgM(int); property public abstract androidx.compose.foundation.layout.GridFlow flow; - property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr int.fr; - property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr float.fr; - property @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr double.fr; + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr int.fr; + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr float.fr; + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Stable public default androidx.compose.foundation.layout.Fr double.fr; } - @kotlin.jvm.JvmInline public final value class GridFlow { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @kotlin.jvm.JvmInline public final value class GridFlow { ctor @KotlinOnly @kotlin.PublishedApi internal GridFlow(int bits); method @BytecodeOnly public static androidx.compose.foundation.layout.GridFlow! box-impl(int); method @BytecodeOnly @kotlin.PublishedApi internal static int constructor-impl(int); @@ -349,55 +352,55 @@ package androidx.compose.foundation.layout { field public static final androidx.compose.foundation.layout.GridFlow.Companion Companion; } - public static final class GridFlow.Companion { - method @BytecodeOnly public int getColumn-ITJdzs4(); - method @BytecodeOnly public int getRow-ITJdzs4(); - property public inline androidx.compose.foundation.layout.GridFlow Column; - property public inline androidx.compose.foundation.layout.GridFlow Row; + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static final class GridFlow.Companion { + method @BytecodeOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public int getColumn-ITJdzs4(); + method @BytecodeOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public int getRow-ITJdzs4(); + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public inline androidx.compose.foundation.layout.GridFlow Column; + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public inline androidx.compose.foundation.layout.GridFlow Row; } - public final class GridKt { - method @BytecodeOnly @androidx.compose.runtime.Composable public static void Grid(kotlin.jvm.functions.Function1, androidx.compose.ui.Modifier?, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); - method @KotlinOnly @androidx.compose.runtime.Composable public static inline void Grid(kotlin.jvm.functions.Function1 config, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1 content); - method public static void columns(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); - method public static void rows(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); + @SuppressCompatibility public final class GridKt { + method @BytecodeOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Composable public static void Grid(kotlin.jvm.functions.Function1, androidx.compose.ui.Modifier?, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); + method @KotlinOnly @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Composable public static inline void Grid(kotlin.jvm.functions.Function1 config, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1 content); + method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static void columns(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); + method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static void rows(androidx.compose.foundation.layout.GridConfigurationScope, androidx.compose.foundation.layout.GridTrackSpec... specs); } - @kotlin.PublishedApi internal final class GridMeasurePolicy implements androidx.compose.ui.layout.MeasurePolicy { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @kotlin.PublishedApi internal final class GridMeasurePolicy implements androidx.compose.ui.layout.MeasurePolicy { ctor public GridMeasurePolicy(androidx.compose.runtime.State> configState); method @KotlinOnly public androidx.compose.ui.layout.MeasureResult measure(androidx.compose.ui.layout.MeasureScope, java.util.List measurables, androidx.compose.ui.unit.Constraints constraints); method @BytecodeOnly public androidx.compose.ui.layout.MeasureResult measure-3p2s80s(androidx.compose.ui.layout.MeasureScope, java.util.List, long); } - @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable @kotlin.jvm.JvmDefaultWithCompatibility public interface GridScope { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable @kotlin.jvm.JvmDefaultWithCompatibility public interface GridScope { method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, optional int row, optional int column, optional int rowSpan, optional int columnSpan, optional androidx.compose.ui.Alignment alignment); method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, kotlin.ranges.IntRange rows, kotlin.ranges.IntRange columns, optional androidx.compose.ui.Alignment alignment); method @BytecodeOnly @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier! gridItem$default(androidx.compose.foundation.layout.GridScope!, androidx.compose.ui.Modifier!, int, int, int, int, androidx.compose.ui.Alignment!, int, Object!); method @BytecodeOnly @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier! gridItem$default(androidx.compose.foundation.layout.GridScope!, androidx.compose.ui.Modifier!, kotlin.ranges.IntRange!, kotlin.ranges.IntRange!, androidx.compose.ui.Alignment!, int, Object!); field public static final androidx.compose.foundation.layout.GridScope.Companion Companion; - field public static final int GridIndexUnspecified = 0; // 0x0 - field public static final int MaxGridIndex = 1000; // 0x3e8 + field @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static final int GridIndexUnspecified = 0; // 0x0 + field @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static final int MaxGridIndex = 1000; // 0x3e8 } - public static final class GridScope.Companion { - property public static int GridIndexUnspecified; - property public static int MaxGridIndex; + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static final class GridScope.Companion { + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static int GridIndexUnspecified; + property @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static int MaxGridIndex; field public static final int GridIndexUnspecified = 0; // 0x0 field public static final int MaxGridIndex = 1000; // 0x3e8 } - @kotlin.PublishedApi internal final class GridScopeInstance implements androidx.compose.foundation.layout.GridScope { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @kotlin.PublishedApi internal final class GridScopeInstance implements androidx.compose.foundation.layout.GridScope { method public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, int row, int column, int rowSpan, int columnSpan, androidx.compose.ui.Alignment alignment); method public androidx.compose.ui.Modifier gridItem(androidx.compose.ui.Modifier, kotlin.ranges.IntRange rows, kotlin.ranges.IntRange columns, androidx.compose.ui.Alignment alignment); } - @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class GridTrackSize implements androidx.compose.foundation.layout.GridTrackSpec { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class GridTrackSize implements androidx.compose.foundation.layout.GridTrackSpec { method @BytecodeOnly public static androidx.compose.foundation.layout.GridTrackSize! box-impl(long); method @BytecodeOnly public long unbox-impl(); field public static final androidx.compose.foundation.layout.GridTrackSize.Companion Companion; } - public static final class GridTrackSize.Companion { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public static final class GridTrackSize.Companion { method @KotlinOnly @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Fixed(androidx.compose.ui.unit.Dp size); method @BytecodeOnly @androidx.compose.runtime.Stable public long Fixed-psSkOvk(float); method @KotlinOnly @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize Flex(@FloatRange(from=0.0) androidx.compose.foundation.layout.Fr weight); @@ -412,7 +415,7 @@ package androidx.compose.foundation.layout { property @androidx.compose.runtime.Stable public androidx.compose.foundation.layout.GridTrackSize MinContent; } - public sealed exhaustive interface GridTrackSpec { + @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalGridApi public sealed exhaustive interface GridTrackSpec { } public final class IntrinsicKt { diff --git a/compose/foundation/foundation-layout/bcv/native/current.txt b/compose/foundation/foundation-layout/bcv/native/current.txt index 7762ea02494b8..b804a99535070 100644 --- a/compose/foundation/foundation-layout/bcv/native/current.txt +++ b/compose/foundation/foundation-layout/bcv/native/current.txt @@ -6,6 +6,10 @@ // - Show declarations: true // Library unique name: +open annotation class androidx.compose.foundation.layout/ExperimentalGridApi : kotlin/Annotation { // androidx.compose.foundation.layout/ExperimentalGridApi|null[0] + constructor () // androidx.compose.foundation.layout/ExperimentalGridApi.|(){}[0] +} + open annotation class androidx.compose.foundation.layout/ExperimentalLayoutApi : kotlin/Annotation { // androidx.compose.foundation.layout/ExperimentalLayoutApi|null[0] constructor () // androidx.compose.foundation.layout/ExperimentalLayoutApi.|(){}[0] } diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt index 7a45cb048ad72..114ac0f029c54 100644 --- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt +++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/GridDemo.kt @@ -13,6 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +@file:OptIn(ExperimentalGridApi::class) + package androidx.compose.foundation.layout.demos import androidx.compose.foundation.background @@ -20,6 +23,7 @@ import androidx.compose.foundation.border import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalGridApi import androidx.compose.foundation.layout.Grid import androidx.compose.foundation.layout.GridFlow import androidx.compose.foundation.layout.GridScope diff --git a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt index 968757438be9d..1b6ef306f4c1d 100644 --- a/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt +++ b/compose/foundation/foundation-layout/src/androidDeviceTest/kotlin/androidx/compose/foundation/layout/GridTest.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalGridApi::class) + package androidx.compose.foundation.layout import androidx.compose.foundation.horizontalScroll diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ExperimentalGridApi.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ExperimentalGridApi.kt new file mode 100644 index 0000000000000..3e9e85b50c624 --- /dev/null +++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ExperimentalGridApi.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.foundation.layout + +@RequiresOptIn( + "This foundation layout API is experimental and is likely to change or be removed in the future." +) +@Retention(AnnotationRetention.BINARY) +annotation class ExperimentalGridApi diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt index 856fe89ebf8e6..6803cf2809d90 100644 --- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt +++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Grid.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalGridApi::class) + package androidx.compose.foundation.layout import androidx.annotation.FloatRange @@ -79,6 +81,7 @@ import kotlin.math.roundToInt * @see GridConfigurationScope */ @Composable +@ExperimentalGridApi inline fun Grid( noinline config: GridConfigurationScope.() -> Unit, modifier: Modifier = Modifier, @@ -105,6 +108,7 @@ inline fun Grid( @LayoutScopeMarker @Immutable @JvmDefaultWithCompatibility +@ExperimentalGridApi interface GridScope { /** * Configures the position, span, and alignment of an element within a [Grid] layout. @@ -187,17 +191,19 @@ interface GridScope { * potentially caused by accidental loop overflows or unreasonably large sparse grid * definitions. */ - const val MaxGridIndex: Int = 1000 + @ExperimentalGridApi const val MaxGridIndex: Int = 1000 + /** * Sentinel value indicating that a grid position (row or column) is not manually specified * and should be determined automatically by the layout flow. */ - const val GridIndexUnspecified: Int = 0 + @ExperimentalGridApi const val GridIndexUnspecified: Int = 0 } } /** Internal implementation of [GridScope]. Stateless object to avoid allocations. */ @PublishedApi +@ExperimentalGridApi internal object GridScopeInstance : GridScope { override fun Modifier.gridItem( @@ -245,6 +251,7 @@ internal object GridScopeInstance : GridScope { * rows, and gaps. */ @LayoutScopeMarker +@ExperimentalGridApi interface GridConfigurationScope : Density { /** @@ -319,21 +326,25 @@ interface GridConfigurationScope : Density { /** Creates an [Fr] unit from an [Int]. */ @Stable + @ExperimentalGridApi val Int.fr: Fr get() = Fr(this.toFloat()) /** Creates an [Fr] unit from a [Float]. */ @Stable + @ExperimentalGridApi val Float.fr: Fr get() = Fr(this) /** Creates an [Fr] unit from a [Double]. */ @Stable + @ExperimentalGridApi val Double.fr: Fr get() = Fr(this.toFloat()) } /** Adds multiple columns with the specified [specs]. */ +@ExperimentalGridApi fun GridConfigurationScope.columns(vararg specs: GridTrackSpec) { for (spec in specs) { if (spec is GridTrackSize) { @@ -343,6 +354,7 @@ fun GridConfigurationScope.columns(vararg specs: GridTrackSpec) { } /** Adds multiple rows with the specified [specs]. */ +@ExperimentalGridApi fun GridConfigurationScope.rows(vararg specs: GridTrackSpec) { for (spec in specs) { if (spec is GridTrackSize) { @@ -353,14 +365,17 @@ fun GridConfigurationScope.rows(vararg specs: GridTrackSpec) { /** Defines the direction in which auto-placed items flow within the grid. */ @JvmInline +@ExperimentalGridApi value class GridFlow @PublishedApi internal constructor(private val bits: Int) { companion object { /** Items are placed filling the first row, then moving to the next row. */ + @ExperimentalGridApi inline val Row get() = GridFlow(0) /** Items are placed filling the first column, then moving to the next column. */ + @ExperimentalGridApi inline val Column get() = GridFlow(1) } @@ -380,6 +395,7 @@ value class GridFlow @PublishedApi internal constructor(private val bits: Int) { * [GridTrackSize.Fixed] and [GridTrackSize.Percentage] tracks have been allocated. */ @JvmInline +@ExperimentalGridApi value class Fr(val value: Float) { override fun toString(): String = "$value.fr" } @@ -390,7 +406,7 @@ value class Fr(val value: Float) { * This allows the configuration DSL to accept [GridTrackSize] items in a vararg (e.g., * `columns(Fixed(10.dp), Flex(1.fr))`), bypassing the Kotlin limitation on value class varargs. */ -sealed interface GridTrackSpec +@ExperimentalGridApi sealed interface GridTrackSpec /** * Defines the size of a track (a row or a column) in a [Grid]. @@ -399,6 +415,7 @@ sealed interface GridTrackSpec */ @Immutable @JvmInline +@ExperimentalGridApi value class GridTrackSize internal constructor(internal val encodedValue: Long) : GridTrackSpec { internal val type: Int @@ -581,6 +598,7 @@ private class GridItemNode( /** A stable MeasurePolicy that reads configuration from a State. */ @PublishedApi +@ExperimentalGridApi internal class GridMeasurePolicy( private val configState: State Unit> ) : MeasurePolicy { From 0f022da1ef0edb6ad52f4d4904779211988d783e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tolga=20Can=20=C3=9Cnal?= Date: Wed, 14 Jan 2026 16:42:00 +0000 Subject: [PATCH 10/19] Include emitInteraction calls in Surface#Press/Release benchmarks As being discussed in aosp/3908200, calls to use interactions happens synchronously. setPressed and isPressed happen in the UI thread and blocks the next frame until it's finished. So, it's useful to measure the performance of processing Press/Release interactions. 7,814 ns 3 allocs SurfaceBenchmark.surface_secondDrawFocusAnimation 69,521 ns 87 allocs SurfaceBenchmark.surface_secondFrameFocusAnimation 362,557 ns 108 allocs SurfaceBenchmark.surface_thirdFrameFocusAnimation 59,598 ns 41 allocs SurfaceBenchmark.surface_firstFrameReleaseAnimation 157,582 ns 216 allocs SurfaceBenchmark.surface_firstFramePressAnimation 57,409 ns 29 allocs SurfaceBenchmark.surface_firstDraw 286,793 ns 28 allocs SurfaceBenchmark.surface_thirdDrawFocusAnimation 671,456 ns 584 allocs SurfaceBenchmark.surface_firstFrame Test: Run benchmark Change-Id: I4c89410a87a3c1cb8158b43386aa20a02e72e769 --- .../xr/glimmer/benchmark/SurfaceBenchmark.kt | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/xr/glimmer/benchmark/src/androidTest/java/androidx/xr/glimmer/benchmark/SurfaceBenchmark.kt b/xr/glimmer/benchmark/src/androidTest/java/androidx/xr/glimmer/benchmark/SurfaceBenchmark.kt index 0193624ece736..7987ef495196c 100644 --- a/xr/glimmer/benchmark/src/androidTest/java/androidx/xr/glimmer/benchmark/SurfaceBenchmark.kt +++ b/xr/glimmer/benchmark/src/androidTest/java/androidx/xr/glimmer/benchmark/SurfaceBenchmark.kt @@ -234,11 +234,12 @@ class SurfaceBenchmark { } /** - * Measures the time to draw the first frame after emitting a [PressInteraction.Press]. This is - * benchmarked on an already-focused surface to isolate the press state change. + * Measures the time to process [PressInteraction.Press] and perform composition, measurement, + * layout, and drawing to render the first frame. This is benchmarked on an already-focused + * surface to isolate the press state change. */ @Test - fun surface_firstFrame_afterPressInteraction() { + fun surface_firstFramePressAnimation() { with(benchmarkRule) { runBenchmarkFor({ SurfaceTestCase(addSurfaceModifierEnabledByDefault = true) }) { runOnUiThread { @@ -248,9 +249,7 @@ class SurfaceBenchmark { val press = PressInteraction.Press(Offset.Zero) measureRepeatedOnUiThread { - runWithMeasurementDisabled { - runBlocking { getTestCase().emitInteraction(press) } - } + runBlocking { getTestCase().emitInteraction(press) } doFrame() @@ -273,11 +272,12 @@ class SurfaceBenchmark { } /** - * Measures the time to draw the first frame after emitting a [PressInteraction.Release]. This - * is benchmarked on an already-focused and pressed surface to isolate the release state change. + * Measures the time to process [PressInteraction.Release] and perform composition, measurement, + * layout, and draw to render the first frame. This is benchmarked on an already-focused and + * pressed surface to isolate the press state change. */ @Test - fun surface_firstFrame_afterReleaseInteraction() { + fun surface_firstFrameReleaseAnimation() { with(benchmarkRule) { runBenchmarkFor({ SurfaceTestCase(addSurfaceModifierEnabledByDefault = true) }) { runOnUiThread { @@ -288,17 +288,15 @@ class SurfaceBenchmark { val press = PressInteraction.Press(Offset.Zero) measureRepeatedOnUiThread { runWithMeasurementDisabled { - runBlocking { - // Emit interaction to start press animation. - getTestCase().emitInteraction(press) - - doFramesUntilNoChangesPending() + // Emit interaction to start press animation. + runBlocking { getTestCase().emitInteraction(press) } - // Emit interaction to trigger release animation - getTestCase().emitInteraction(PressInteraction.Release(press)) - } + doFramesUntilNoChangesPending() } + // Emit interaction to trigger release animation + runBlocking { getTestCase().emitInteraction(PressInteraction.Release(press)) } + doFrame() runWithMeasurementDisabled { From 2f6373180648a0137e2167359289804a636b76cd Mon Sep 17 00:00:00 2001 From: jbwoods Date: Wed, 14 Jan 2026 18:51:17 +0000 Subject: [PATCH 11/19] Fix SafeArgs configuration caching issue Calling `variant` within the lambda provider causes an issue with serialization. Extract `flavorName`, `buildType`, and `name` into local variables to prevent the `Variant` object from being captured within the `map` lambda. RelNote: "Navigation SafeArgs no longer has configuration caching issues when being used with Google Services." Test: tested with sample project Bug: 458071608 Change-Id: I57cda144676def56d47091ed93e25c9fe068d6f3 --- .../navigation/safeargs/gradle/SafeArgsPlugin.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/navigation/navigation-safe-args-gradle-plugin/src/main/kotlin/androidx/navigation/safeargs/gradle/SafeArgsPlugin.kt b/navigation/navigation-safe-args-gradle-plugin/src/main/kotlin/androidx/navigation/safeargs/gradle/SafeArgsPlugin.kt index 9c4e9f89353cc..ae1517a40c18f 100644 --- a/navigation/navigation-safe-args-gradle-plugin/src/main/kotlin/androidx/navigation/safeargs/gradle/SafeArgsPlugin.kt +++ b/navigation/navigation-safe-args-gradle-plugin/src/main/kotlin/androidx/navigation/safeargs/gradle/SafeArgsPlugin.kt @@ -115,6 +115,9 @@ abstract class SafeArgsPlugin protected constructor() : Plugin { } private fun navigationFiles(variant: Variant): Provider> { + val flavorName = variant.flavorName + val buildType = variant.buildType + val name = variant.name return variant.sources.res!!.all.map { resSources -> resSources .flatten() @@ -132,13 +135,13 @@ abstract class SafeArgsPlugin protected constructor() : Plugin { ?: entry.value.minBy { file -> when { // matches variant name exactly - file.path.substringBefore("/res/").endsWith(variant.name) -> 0 + file.path.substringBefore("/res/").endsWith(name) -> 0 // matches variant flavor - variant.flavorName?.let { + flavorName?.let { file.path.substringBefore("/res/").endsWith(it) } == true -> 1 // matches variant buildType - variant.buildType?.let { + buildType?.let { file.path.substringBefore("/res/").endsWith(it) } == true -> 2 // fall back to main From 081102181c746d1554816e645a08260d307b76a7 Mon Sep 17 00:00:00 2001 From: Mariano Martin Date: Tue, 13 Jan 2026 16:30:38 -0500 Subject: [PATCH 12/19] [TimeInput] Remove additional padding, now that it's not needed Reverting a regression introduced on Dec. 8th in https://android-review.googlesource.com/c/platform/frameworks/support/+/3881841 Bug: 475276359 Test: Tested on g3 tests Change-Id: Iad17ae7fab5f3a4ff4a9d59f28009bfae5c5f385 (cherry picked from commit c3939bb15213aa868a5a7589afd60a66be7bd787) --- .../kotlin/androidx/compose/material3/TimePicker.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt index 5a6ab1692f189..40e21e032e19c 100644 --- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt +++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt @@ -1122,10 +1122,7 @@ private fun TimeInputImpl(modifier: Modifier, colors: TimePickerColors, state: T userOverride.value = true } - Row( - modifier = modifier.padding(bottom = TimeInputBottomPadding), - verticalAlignment = Alignment.Top, - ) { + Row(modifier = modifier, verticalAlignment = Alignment.Top) { val textStyle = TimeInputTokens.TimeFieldLabelTextFont.value.copy( textAlign = TextAlign.Center, @@ -2033,7 +2030,7 @@ private fun SupportingText( else TimeInputTokens.TimeFieldSupportingTextColor.value Text( - modifier = modifier.offset(y = SupportLabelTop).clearAndSetSemantics {}, + modifier = modifier.padding(top = SupportLabelTop).clearAndSetSemantics {}, text = text, color = color, minLines = 2, @@ -2285,7 +2282,6 @@ private val ClockFaceBottomMargin = 24.dp private val DisplaySeparatorWidth = 24.dp private val SupportLabelTop = 7.dp -private val TimeInputBottomPadding = 8.dp private val MaxDistance = 74.dp private val MinimumInteractiveSize = 48.dp private val Minutes = intListOf(0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55) From 875779ad9d75145852a1168f44d85e0b0e4805f4 Mon Sep 17 00:00:00 2001 From: pfthomas Date: Fri, 12 Dec 2025 13:53:10 -0500 Subject: [PATCH 13/19] [SearchBar] Fullscreen Expressive animations Test: manual RelNote: "Added support for animationSpecForContentExpand and animationSpecForContentCollapse." Change-Id: I033a5fc3e829c992a5c051d74390ee2ddfe1329d --- compose/material3/material3/api/current.txt | 4 + .../material3/api/restricted_current.txt | 4 + .../material3/samples/SearchBarSamples.kt | 79 +++++-- .../androidx/compose/material3/SearchBar.kt | 219 ++++++++++++++++-- 4 files changed, 261 insertions(+), 45 deletions(-) diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt index bb96e91e775ac..bc3a017cb8cd7 100644 --- a/compose/material3/material3/api/current.txt +++ b/compose/material3/material3/api/current.txt @@ -3074,6 +3074,8 @@ package androidx.compose.material3 { method @BytecodeOnly @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SearchBar-nbWgWpA(androidx.compose.material3.SearchBarState, kotlin.jvm.functions.Function2, androidx.compose.ui.Modifier?, androidx.compose.ui.graphics.Shape?, androidx.compose.material3.SearchBarColors?, float, float, androidx.compose.runtime.Composer?, int, int); method @KotlinOnly @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopSearchBar(androidx.compose.material3.SearchBarState state, kotlin.jvm.functions.Function0 inputField, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.SearchBarColors colors, optional androidx.compose.ui.unit.Dp tonalElevation, optional androidx.compose.ui.unit.Dp shadowElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.SearchBarScrollBehavior? scrollBehavior); method @BytecodeOnly @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopSearchBar-qKj4JfE(androidx.compose.material3.SearchBarState, kotlin.jvm.functions.Function2, androidx.compose.ui.Modifier?, androidx.compose.ui.graphics.Shape?, androidx.compose.material3.SearchBarColors?, float, float, androidx.compose.foundation.layout.WindowInsets?, androidx.compose.material3.SearchBarScrollBehavior?, androidx.compose.runtime.Composer?, int, int); + method @KotlinOnly @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SearchBarState rememberContainedSearchBarState(optional androidx.compose.material3.SearchBarValue initialValue, optional androidx.compose.animation.core.AnimationSpec animationSpecForExpand, optional androidx.compose.animation.core.AnimationSpec animationSpecForCollapse, optional androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeIn, optional androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeOut); + method @BytecodeOnly @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SearchBarState rememberContainedSearchBarState(androidx.compose.material3.SearchBarValue?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.runtime.Composer?, int, int); method @KotlinOnly @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SearchBarState rememberSearchBarState(optional androidx.compose.material3.SearchBarValue initialValue, optional androidx.compose.animation.core.AnimationSpec animationSpecForExpand, optional androidx.compose.animation.core.AnimationSpec animationSpecForCollapse); method @BytecodeOnly @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SearchBarState rememberSearchBarState(androidx.compose.material3.SearchBarValue?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.runtime.Composer?, int, int); } @@ -3095,6 +3097,7 @@ package androidx.compose.material3 { @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SearchBarState { ctor public SearchBarState(androidx.compose.material3.SearchBarValue initialValue, androidx.compose.animation.core.AnimationSpec animationSpecForExpand, androidx.compose.animation.core.AnimationSpec animationSpecForCollapse); + ctor public SearchBarState(androidx.compose.material3.SearchBarValue initialValue, androidx.compose.animation.core.AnimationSpec animationSpecForExpand, androidx.compose.animation.core.AnimationSpec animationSpecForCollapse, androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeIn, androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeOut); method public suspend Object? animateToCollapsed(kotlin.coroutines.Continuation); method public suspend Object? animateToExpanded(kotlin.coroutines.Continuation); method @InaccessibleFromKotlin public androidx.compose.ui.layout.LayoutCoordinates? getCollapsedCoords(); @@ -3114,6 +3117,7 @@ package androidx.compose.material3 { @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final class SearchBarState.Companion { method public androidx.compose.runtime.saveable.Saver Saver(androidx.compose.animation.core.AnimationSpec animationSpecForExpand, androidx.compose.animation.core.AnimationSpec animationSpecForCollapse); + method public androidx.compose.runtime.saveable.Saver Saver(androidx.compose.animation.core.AnimationSpec animationSpecForExpand, androidx.compose.animation.core.AnimationSpec animationSpecForCollapse, androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeIn, androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeOut); } @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SearchBarValue { diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt index bb96e91e775ac..bc3a017cb8cd7 100644 --- a/compose/material3/material3/api/restricted_current.txt +++ b/compose/material3/material3/api/restricted_current.txt @@ -3074,6 +3074,8 @@ package androidx.compose.material3 { method @BytecodeOnly @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SearchBar-nbWgWpA(androidx.compose.material3.SearchBarState, kotlin.jvm.functions.Function2, androidx.compose.ui.Modifier?, androidx.compose.ui.graphics.Shape?, androidx.compose.material3.SearchBarColors?, float, float, androidx.compose.runtime.Composer?, int, int); method @KotlinOnly @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopSearchBar(androidx.compose.material3.SearchBarState state, kotlin.jvm.functions.Function0 inputField, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.SearchBarColors colors, optional androidx.compose.ui.unit.Dp tonalElevation, optional androidx.compose.ui.unit.Dp shadowElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.SearchBarScrollBehavior? scrollBehavior); method @BytecodeOnly @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopSearchBar-qKj4JfE(androidx.compose.material3.SearchBarState, kotlin.jvm.functions.Function2, androidx.compose.ui.Modifier?, androidx.compose.ui.graphics.Shape?, androidx.compose.material3.SearchBarColors?, float, float, androidx.compose.foundation.layout.WindowInsets?, androidx.compose.material3.SearchBarScrollBehavior?, androidx.compose.runtime.Composer?, int, int); + method @KotlinOnly @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SearchBarState rememberContainedSearchBarState(optional androidx.compose.material3.SearchBarValue initialValue, optional androidx.compose.animation.core.AnimationSpec animationSpecForExpand, optional androidx.compose.animation.core.AnimationSpec animationSpecForCollapse, optional androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeIn, optional androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeOut); + method @BytecodeOnly @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SearchBarState rememberContainedSearchBarState(androidx.compose.material3.SearchBarValue?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.runtime.Composer?, int, int); method @KotlinOnly @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SearchBarState rememberSearchBarState(optional androidx.compose.material3.SearchBarValue initialValue, optional androidx.compose.animation.core.AnimationSpec animationSpecForExpand, optional androidx.compose.animation.core.AnimationSpec animationSpecForCollapse); method @BytecodeOnly @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SearchBarState rememberSearchBarState(androidx.compose.material3.SearchBarValue?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.animation.core.AnimationSpec?, androidx.compose.runtime.Composer?, int, int); } @@ -3095,6 +3097,7 @@ package androidx.compose.material3 { @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SearchBarState { ctor public SearchBarState(androidx.compose.material3.SearchBarValue initialValue, androidx.compose.animation.core.AnimationSpec animationSpecForExpand, androidx.compose.animation.core.AnimationSpec animationSpecForCollapse); + ctor public SearchBarState(androidx.compose.material3.SearchBarValue initialValue, androidx.compose.animation.core.AnimationSpec animationSpecForExpand, androidx.compose.animation.core.AnimationSpec animationSpecForCollapse, androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeIn, androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeOut); method public suspend Object? animateToCollapsed(kotlin.coroutines.Continuation); method public suspend Object? animateToExpanded(kotlin.coroutines.Continuation); method @InaccessibleFromKotlin public androidx.compose.ui.layout.LayoutCoordinates? getCollapsedCoords(); @@ -3114,6 +3117,7 @@ package androidx.compose.material3 { @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final class SearchBarState.Companion { method public androidx.compose.runtime.saveable.Saver Saver(androidx.compose.animation.core.AnimationSpec animationSpecForExpand, androidx.compose.animation.core.AnimationSpec animationSpecForCollapse); + method public androidx.compose.runtime.saveable.Saver Saver(androidx.compose.animation.core.AnimationSpec animationSpecForExpand, androidx.compose.animation.core.AnimationSpec animationSpecForCollapse, androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeIn, androidx.compose.animation.core.AnimationSpec animationSpecForContentFadeOut); } @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SearchBarValue { diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SearchBarSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SearchBarSamples.kt index 9455a24ddd5e5..de4ad35e8429b 100644 --- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SearchBarSamples.kt +++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SearchBarSamples.kt @@ -19,6 +19,10 @@ package androidx.compose.material3.samples import androidx.annotation.Sampled +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideIn +import androidx.compose.animation.slideOut import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -46,6 +50,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme.motionScheme import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Scaffold import androidx.compose.material3.SearchBar @@ -56,6 +61,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.rememberContainedSearchBarState import androidx.compose.material3.rememberSearchBarState import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable @@ -65,6 +71,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -107,7 +114,7 @@ fun SimpleSearchBarSample() { @Composable fun FullScreenSearchBarScaffoldSample() { val textFieldState = rememberTextFieldState() - val searchBarState = rememberSearchBarState() + val searchBarState = rememberContainedSearchBarState() val scope = rememberCoroutineScope() val scrollBehavior = SearchBarDefaults.enterAlwaysSearchBarScrollBehavior() val appBarWithSearchColors = @@ -137,8 +144,8 @@ fun FullScreenSearchBarScaffoldSample() { state = searchBarState, colors = appBarWithSearchColors, inputField = inputField, - navigationIcon = { SampleNavigationIcon() }, - actions = { SampleActions() }, + navigationIcon = { SampleNavigationIcon(searchBarState, isAnimated = true) }, + actions = { SampleActions(searchBarState, isAnimated = true) }, ) ExpandedFullScreenContainedSearchBar( state = searchBarState, @@ -199,8 +206,8 @@ fun DockedSearchBarScaffoldSample() { state = searchBarState, colors = appBarWithSearchColors, inputField = inputField, - navigationIcon = { SampleNavigationIcon() }, - actions = { SampleActions() }, + navigationIcon = { SampleNavigationIcon(searchBarState) }, + actions = { SampleActions(searchBarState) }, ) ExpandedDockedSearchBarWithGap(state = searchBarState, inputField = inputField) { SampleSearchResults( @@ -273,28 +280,58 @@ private fun SampleTrailingIcon() = } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable -private fun SampleNavigationIcon() = - TooltipBox( - positionProvider = - TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above), - tooltip = { PlainTooltip { Text("Menu") } }, - state = rememberTooltipState(), +private fun SampleNavigationIcon(state: SearchBarState, isAnimated: Boolean = false) = + AnimatedVisibility( + visible = !isAnimated || state.targetValue == SearchBarValue.Collapsed, + enter = + slideIn( + animationSpec = motionScheme.fastSpatialSpec(), + initialOffset = { IntOffset(-it.width, 0) }, + ), + exit = + slideOut( + animationSpec = tween(durationMillis = 150, delayMillis = 0), + targetOffset = { IntOffset(-it.width, 0) }, + ), ) { - IconButton(onClick = { /* doSomething() */ }) { - Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu") + TooltipBox( + positionProvider = + TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above), + tooltip = { PlainTooltip { Text("Menu") } }, + state = rememberTooltipState(), + ) { + IconButton(onClick = { /* doSomething() */ }) { + Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu") + } } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable -private fun SampleActions() = - TooltipBox( - positionProvider = - TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above), - tooltip = { PlainTooltip { Text("Account") } }, - state = rememberTooltipState(), +private fun SampleActions(state: SearchBarState, isAnimated: Boolean = false) = + AnimatedVisibility( + visible = !isAnimated || state.targetValue == SearchBarValue.Collapsed, + enter = + slideIn( + animationSpec = motionScheme.fastSpatialSpec(), + initialOffset = { IntOffset(it.width, 0) }, + ), + exit = + slideOut( + animationSpec = tween(durationMillis = 150, delayMillis = 0), + targetOffset = { IntOffset(it.width, 0) }, + ), ) { - IconButton(onClick = { /* doSomething() */ }) { - Icon(imageVector = Icons.Default.AccountCircle, contentDescription = "Account") + TooltipBox( + positionProvider = + TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above), + tooltip = { PlainTooltip { Text("Account") } }, + state = rememberTooltipState(), + ) { + IconButton(onClick = { /* doSomething() */ }) { + Icon(imageVector = Icons.Default.AccountCircle, contentDescription = "Account") + } } } diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SearchBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SearchBar.kt index afb1565d35e76..a887023d18dad 100644 --- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SearchBar.kt +++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SearchBar.kt @@ -32,6 +32,7 @@ import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.animateDecay import androidx.compose.animation.core.animateTo +import androidx.compose.animation.core.snap import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn @@ -137,6 +138,7 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.focus.FocusDirection @@ -455,7 +457,11 @@ fun AppBarWithSearch( ) } } - Box(modifier = Modifier.weight(1f)) { + val isVisible = + !state.expandsToFullScreen || + state.currentValue == SearchBarValue.Collapsed || + state.targetValue == SearchBarValue.Expanded // prevent flickering + Box(modifier = Modifier.weight(1f).alpha(if (isVisible) 1f else 0f)) { SearchBar( state = state, inputField = inputField, @@ -536,6 +542,7 @@ fun ExpandedFullScreenContainedSearchBar( properties: DialogProperties = DialogProperties(), content: @Composable ColumnScope.() -> Unit, ) { + state.expandsToFullScreen = true ExpandedFullScreenSearchBarImpl(state = state, properties = properties) { focusRequester, predictiveBackState -> @@ -557,6 +564,7 @@ fun ExpandedFullScreenContainedSearchBar( tonalElevation = tonalElevation, shadowElevation = shadowElevation, windowInsets = windowInsets(), + isContained = true, content = content, ) } @@ -603,6 +611,7 @@ fun ExpandedFullScreenSearchBar( properties: DialogProperties = DialogProperties(), content: @Composable ColumnScope.() -> Unit, ) { + state.expandsToFullScreen = true ExpandedFullScreenSearchBarImpl(state = state, properties = properties) { focusRequester, predictiveBackState -> @@ -624,6 +633,7 @@ fun ExpandedFullScreenSearchBar( tonalElevation = tonalElevation, shadowElevation = shadowElevation, windowInsets = windowInsets(), + isContained = false, content = { HorizontalDivider(color = colors.dividerColor) content() @@ -1064,9 +1074,12 @@ enum class SearchBarValue { @Stable class SearchBarState private constructor( - private val animatable: Animatable, + internal val animatable: Animatable, + private val contentAnimatable: Animatable, private val animationSpecForExpand: AnimationSpec, private val animationSpecForCollapse: AnimationSpec, + private val animationSpecForContentFadeIn: AnimationSpec, + private val animationSpecForContentFadeOut: AnimationSpec, ) { /** * Construct a [SearchBarState]. @@ -1082,10 +1095,44 @@ private constructor( ) : this( animatable = Animatable(if (initialValue == SearchBarValue.Expanded) Expanded else Collapsed), + contentAnimatable = + Animatable(if (initialValue == SearchBarValue.Expanded) Expanded else Collapsed), + animationSpecForExpand = animationSpecForExpand, + animationSpecForCollapse = animationSpecForCollapse, + animationSpecForContentFadeIn = snap(), + animationSpecForContentFadeOut = snap(), + ) + + /** + * Construct a [SearchBarState]. + * + * @param initialValue the initial value of whether the search bar is collapsed or expanded. + * @param animationSpecForExpand the animation spec used when the search bar expands. + * @param animationSpecForCollapse the animation spec used when the search bar collapses. + * @param animationSpecForContentFadeIn the animation spec used for the content when the search + * bar expands. + * @param animationSpecForContentFadeOut the animation spec used for the content when the search + * bar collapses. + */ + constructor( + initialValue: SearchBarValue, + animationSpecForExpand: AnimationSpec, + animationSpecForCollapse: AnimationSpec, + animationSpecForContentFadeIn: AnimationSpec, + animationSpecForContentFadeOut: AnimationSpec, + ) : this( + animatable = + Animatable(if (initialValue == SearchBarValue.Expanded) Expanded else Collapsed), + contentAnimatable = + Animatable(if (initialValue == SearchBarValue.Expanded) Expanded else Collapsed), animationSpecForExpand = animationSpecForExpand, animationSpecForCollapse = animationSpecForCollapse, + animationSpecForContentFadeIn = animationSpecForContentFadeIn, + animationSpecForContentFadeOut = animationSpecForContentFadeOut, ) + internal var expandsToFullScreen by mutableStateOf(false) + /** * The layout coordinates, if available, of the search bar when it is collapsed. Used to * coordinate the expansion animation. @@ -1100,6 +1147,10 @@ private constructor( val progress: Float get() = animatable.value.coerceIn(0f, 1f) + @get:FloatRange(from = 0.0, to = 1.0) + internal val contentProgress: Float + get() = contentAnimatable.value.coerceIn(0f, 1f) + /** Whether the state is currently animating */ val isAnimating: Boolean get() = animatable.isRunning @@ -1129,10 +1180,18 @@ private constructor( /** Animate the search bar to its expanded state. */ suspend fun animateToExpanded() { animatable.animateTo(targetValue = Expanded, animationSpec = animationSpecForExpand) + contentAnimatable.animateTo( + targetValue = Expanded, + animationSpec = animationSpecForContentFadeIn, + ) } /** Animate the search bar to its collapsed state. */ suspend fun animateToCollapsed() { + contentAnimatable.animateTo( + targetValue = Collapsed, + animationSpec = animationSpecForContentFadeOut, + ) animatable.animateTo(targetValue = Collapsed, animationSpec = animationSpecForCollapse) } @@ -1154,12 +1213,36 @@ private constructor( animationSpecForCollapse: AnimationSpec, ): Saver = listSaver( - save = { listOf(it.progress) }, + save = { listOf(it.progress, it.contentProgress) }, restore = { SearchBarState( animatable = Animatable(it[0], Float.VectorConverter), + contentAnimatable = Animatable(it[1], Float.VectorConverter), animationSpecForExpand = animationSpecForExpand, animationSpecForCollapse = animationSpecForCollapse, + animationSpecForContentFadeIn = snap(), + animationSpecForContentFadeOut = snap(), + ) + }, + ) + + /** The default [Saver] implementation for [SearchBarState]. */ + fun Saver( + animationSpecForExpand: AnimationSpec, + animationSpecForCollapse: AnimationSpec, + animationSpecForContentFadeIn: AnimationSpec, + animationSpecForContentFadeOut: AnimationSpec, + ): Saver = + listSaver( + save = { listOf(it.progress, it.contentProgress) }, + restore = { + SearchBarState( + animatable = Animatable(it[0], Float.VectorConverter), + contentAnimatable = Animatable(it[1], Float.VectorConverter), + animationSpecForExpand = animationSpecForExpand, + animationSpecForCollapse = animationSpecForCollapse, + animationSpecForContentFadeIn = animationSpecForContentFadeIn, + animationSpecForContentFadeOut = animationSpecForContentFadeOut, ) }, ) @@ -1198,6 +1281,52 @@ fun rememberSearchBarState( } } +/** + * Create and remember a [SearchBarState] to use in conjunction with + * [ExpandedFullScreenContainedSearchBar]. + * + * @param initialValue the initial value of whether the search bar is collapsed or expanded. + * @param animationSpecForExpand the animation spec used when the search bar expands. + * @param animationSpecForCollapse the animation spec used when the search bar collapses. + * @param animationSpecForContentFadeIn the animation spec used for the content when the search bar + * expands. + * @param animationSpecForContentFadeOut the animation spec used for the content when the search bar + * collapses. + */ +@ExperimentalMaterial3Api +@Composable +fun rememberContainedSearchBarState( + initialValue: SearchBarValue = SearchBarValue.Collapsed, + animationSpecForExpand: AnimationSpec = MotionSchemeKeyTokens.FastSpatial.value(), + animationSpecForCollapse: AnimationSpec = MotionSchemeKeyTokens.FastSpatial.value(), + animationSpecForContentFadeIn: AnimationSpec = MotionSchemeKeyTokens.SlowEffects.value(), + animationSpecForContentFadeOut: AnimationSpec = + MotionSchemeKeyTokens.DefaultEffects.value(), +): SearchBarState { + return rememberSaveable( + initialValue, + animationSpecForExpand, + animationSpecForCollapse, + animationSpecForContentFadeIn, + animationSpecForContentFadeOut, + saver = + Saver( + animationSpecForExpand = animationSpecForExpand, + animationSpecForCollapse = animationSpecForCollapse, + animationSpecForContentFadeIn = animationSpecForContentFadeIn, + animationSpecForContentFadeOut = animationSpecForContentFadeOut, + ), + ) { + SearchBarState( + initialValue = initialValue, + animationSpecForExpand = animationSpecForExpand, + animationSpecForCollapse = animationSpecForCollapse, + animationSpecForContentFadeIn = animationSpecForContentFadeIn, + animationSpecForContentFadeOut = animationSpecForContentFadeOut, + ) + } +} + /** * A [SearchBarScrollBehavior] defines how a search bar should behave when the content beneath it is * scrolled. @@ -1615,7 +1744,7 @@ object SearchBarDefaults { @Composable fun containedColors(state: SearchBarState): SearchBarColors { val containerColor = - if (state.targetValue == SearchBarValue.Expanded) { + if (state.currentValue == SearchBarValue.Expanded) { fullScreenContainedSearchBarColor } else { collapsedContainedSearchBarColor @@ -3147,6 +3276,7 @@ private fun FullScreenSearchBarLayout( tonalElevation: Dp, shadowElevation: Dp, windowInsets: WindowInsets, + isContained: Boolean, content: @Composable ColumnScope.() -> Unit, ) { val backEvent by remember { derivedStateOf { predictiveBackState.value } } @@ -3252,8 +3382,15 @@ private fun FullScreenSearchBarLayout( .coerceAtLeast(collapsedHeight) val endWidth = lerp(constraints.maxWidth, predictiveBackEndWidth, predictiveBackProgress) val endHeight = lerp(constraints.maxHeight, predictiveBackEndHeight, predictiveBackProgress) - val width = constraints.constrainWidth(lerp(collapsedWidth, endWidth, state.progress)) - val height = constraints.constrainHeight(lerp(collapsedHeight, endHeight, state.progress)) + val width: Int + val height: Int + if (isContained) { + width = endWidth + height = endHeight + } else { + width = constraints.constrainWidth(lerp(collapsedWidth, endWidth, state.progress)) + height = constraints.constrainHeight(lerp(collapsedHeight, endHeight, state.progress)) + } val surfaceMeasurable = measurables.fastFirst { it.layoutId == LayoutIdSurface } val surfacePlaceable = surfaceMeasurable.measure(Constraints.fixed(width, height)) @@ -3262,21 +3399,29 @@ private fun FullScreenSearchBarLayout( inputFieldPadding.calculateStartPadding(this@Layout.layoutDirection).roundToPx() val endPadding = inputFieldPadding.calculateEndPadding(this@Layout.layoutDirection).roundToPx() - val animatedStartPadding = lerp(0, startPadding, state.progress) - val animatedEndPadding = lerp(0, endPadding, state.progress) - val paddedInputFieldWidth = width - animatedStartPadding - animatedEndPadding + val paddedInputFieldWidth = + lerp(collapsedWidth, width - startPadding - endPadding, state.animatable.value) val inputFieldMeasurable = measurables.fastFirst { it.layoutId == LayoutIdInputField } val inputFieldPlaceable = inputFieldMeasurable.measure(Constraints.fixed(paddedInputFieldWidth, collapsedHeight)) - val topPadding = unconsumedInsets.getTop(this@Layout) + SearchBarVerticalPadding.roundToPx() - val bottomPadding = SearchBarVerticalPadding.roundToPx() + val topPadding = + unconsumedInsets.getTop(this@Layout) + + if (isContained) { + AppBarWithSearchVerticalPadding.roundToPx() + } else { + SearchBarVerticalPadding.roundToPx() + } val animatedTopPadding = lerp(0, topPadding, min(state.progress, 1 - predictiveBackProgress)) - val animatedBottomPadding = lerp(0, bottomPadding, state.progress) + val bottomPadding = + if (isContained) { + SearchBarVerticalPadding.roundToPx() + } else { + lerp(0, SearchBarVerticalPadding.roundToPx(), state.progress) + } - val paddedInputFieldHeight = - inputFieldPlaceable.height + animatedTopPadding + animatedBottomPadding + val paddedInputFieldHeight = inputFieldPlaceable.height + animatedTopPadding + bottomPadding val contentMeasurable = measurables.fastFirst { it.layoutId == LayoutIdSearchContent } val contentPlaceable = contentMeasurable.measure( @@ -3315,26 +3460,52 @@ private fun FullScreenSearchBarLayout( .coerceAtMost(state.collapsedBounds.top) } - val endOffsetX = + val predictiveBackOffsetX = lerp(0, lastInProgressValue.value?.endOffsetX() ?: 0, predictiveBackProgress) - val endOffsetY = + val offsetX = + if (isContained) { + predictiveBackOffsetX + } else { + lerp(state.collapsedBounds.left, predictiveBackOffsetX, state.progress) + } + val currentCenterX = + lerp( + state.collapsedBounds.center.x.toFloat(), + offsetX + width / 2f, + state.animatable.value, + ) + val offsetY = lerp(0, lastInProgressValue.value?.endOffsetY() ?: 0, predictiveBackProgress) - val offsetX = lerp(state.collapsedBounds.left, endOffsetX, state.progress) - val offsetY = lerp(state.collapsedBounds.top, endOffsetY, state.progress) + val animatedOffsetY = lerp(state.collapsedBounds.top, offsetY, state.progress) - surfacePlaceable.place(x = offsetX, y = offsetY) + surfacePlaceable.placeWithLayer( + x = offsetX, + y = if (isContained) offsetY else animatedOffsetY, + layerBlock = { + if (isContained) { + alpha = state.progress + } + }, + ) inputFieldPlaceable.place( - x = offsetX + animatedStartPadding, - y = offsetY + animatedTopPadding, + x = (currentCenterX - inputFieldPlaceable.width / 2f).roundToInt(), + y = animatedOffsetY + animatedTopPadding, ) contentPlaceable.placeWithLayer( x = offsetX, y = - offsetY + + animatedOffsetY + animatedTopPadding + inputFieldPlaceable.height + - animatedBottomPadding, - layerBlock = { alpha = state.progress }, + bottomPadding, + layerBlock = { + alpha = + if (isContained) { + state.contentProgress + } else { + state.progress + } + }, ) } } From 4db13ecd60908ebf1b5b632cc9d06d929f517c90 Mon Sep 17 00:00:00 2001 From: Yashwanth Gajji Date: Fri, 9 Jan 2026 11:59:06 -0800 Subject: [PATCH 14/19] Add rtl awareness to offset subspace modifier This commit changes the current offset modifier to respond for layout direction. Also adds a new absoluteOffset modifier that doesn't consider layout direction. Bug: 474409165 Test: Added Unit tests for RTL layout direction and absoluteOffset Change-Id: Ia417533759b6cf21d32c5925a99ccdd36031db4a --- .../xr/compose/subspace/layout/Offset.kt | 68 +++++++++++--- .../xr/compose/subspace/layout/OffsetTest.kt | 92 +++++++++++++++++-- 2 files changed, 141 insertions(+), 19 deletions(-) diff --git a/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/layout/Offset.kt b/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/layout/Offset.kt index f3455b8322b7c..e0a9832656269 100644 --- a/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/layout/Offset.kt +++ b/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/layout/Offset.kt @@ -16,6 +16,7 @@ package androidx.xr.compose.subspace.layout +import androidx.annotation.RestrictTo import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.xr.compose.subspace.node.SubspaceLayoutModifierNode @@ -28,54 +29,93 @@ import androidx.xr.runtime.math.Vector3 /** * Offset the content by ([x] dp, [y] dp, [z] dp). The offsets can be positive as well as * non-positive. + * + * This modifier will automatically adjust the horizontal offset according to the layout direction: + * when the layout direction is LTR, positive [x] offsets will move the content to the right and + * when the layout direction is RTL, positive [x] offsets will move the content to the left. For a + * modifier that offsets without considering layout direction, see [absoluteOffset]. + * + * @see absoluteOffset */ public fun SubspaceModifier.offset(x: Dp = 0.dp, y: Dp = 0.dp, z: Dp = 0.dp): SubspaceModifier = - this then SubspaceOffsetElement(x = x, y = y, z = z) + this then SubspaceOffsetElement(x = x, y = y, z = z, rtlAware = true) + +/** + * Offset the content by ([x] dp, [y] dp, [z] dp) without considering layout direction. The offsets + * can be positive as well as non-positive. + * + * This modifier will not consider layout direction when calculating the position of the content: a + * positive [x] offset will always move the content to the right. For a modifier that considers the + * layout direction when applying the offset, see [offset]. + * + * @see offset + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +public fun SubspaceModifier.absoluteOffset( + x: Dp = 0.dp, + y: Dp = 0.dp, + z: Dp = 0.dp, +): SubspaceModifier = this then SubspaceOffsetElement(x = x, y = y, z = z, rtlAware = false) -private class SubspaceOffsetElement(public val x: Dp, public val y: Dp, public val z: Dp) : +private class SubspaceOffsetElement(val x: Dp, val y: Dp, val z: Dp, val rtlAware: Boolean) : SubspaceModifierNodeElement() { override fun create(): OffsetNode { - return OffsetNode(x, y, z) + return OffsetNode(x, y, z, rtlAware) } override fun update(node: OffsetNode) { - node.update(x, y, z) + node.update(x, y, z, rtlAware) } override fun hashCode(): Int { var result = x.hashCode() result = 31 * result + y.hashCode() result = 31 * result + z.hashCode() + result = 31 * result + rtlAware.hashCode() + return result } + /* + * TODO(b/475896820): Add unit tests for hashCode and equals for all *Element classes in + * SubspaceModifier APIs + */ override fun equals(other: Any?): Boolean { if (this === other) return true val otherElement = other as? OffsetNode ?: return false - return x == otherElement.x && y == otherElement.y && z == otherElement.z + return x == otherElement.x && + y == otherElement.y && + z == otherElement.z && + rtlAware == otherElement.rtlAware } } -private class OffsetNode(public var x: Dp, public var y: Dp, public var z: Dp) : +private class OffsetNode(var x: Dp, var y: Dp, var z: Dp, var rtlAware: Boolean) : SubspaceLayoutModifierNode, SubspaceModifier.Node() { override val shouldAutoInvalidate: Boolean = false - fun update(x: Dp, y: Dp, z: Dp) { - if (this.x != x || this.y != y || this.z != z) invalidatePlacement() + fun update(x: Dp, y: Dp, z: Dp, rtlAware: Boolean) { + if (this.x != x || this.y != y || this.z != z || this.rtlAware != rtlAware) + invalidatePlacement() this.x = x this.y = y this.z = z + this.rtlAware = rtlAware } override fun SubspaceMeasureScope.measure( measurable: SubspaceMeasurable, constraints: VolumeConstraints, ): SubspaceMeasureResult { - val placeable = measurable.measure(constraints) - return layout(placeable.measuredWidth, placeable.measuredHeight, placeable.measuredDepth) { - placeable.place( + val subspacePlaceable = measurable.measure(constraints) + return layout( + subspacePlaceable.measuredWidth, + subspacePlaceable.measuredHeight, + subspacePlaceable.measuredDepth, + ) { + val pose = Pose( Vector3( x.roundToPx().toFloat(), @@ -83,7 +123,11 @@ private class OffsetNode(public var x: Dp, public var y: Dp, public var z: Dp) : z.roundToPx().toFloat(), ) ) - ) + if (rtlAware) { + subspacePlaceable.placeRelative(pose) + } else { + subspacePlaceable.place(pose) + } } } } diff --git a/xr/compose/compose/src/test/kotlin/androidx/xr/compose/subspace/layout/OffsetTest.kt b/xr/compose/compose/src/test/kotlin/androidx/xr/compose/subspace/layout/OffsetTest.kt index a6648c9d0e41c..9b8f979b4d243 100644 --- a/xr/compose/compose/src/test/kotlin/androidx/xr/compose/subspace/layout/OffsetTest.kt +++ b/xr/compose/compose/src/test/kotlin/androidx/xr/compose/subspace/layout/OffsetTest.kt @@ -18,15 +18,18 @@ package androidx.xr.compose.subspace.layout import androidx.compose.material3.Button import androidx.compose.material3.Text +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.xr.compose.spatial.Subspace @@ -72,7 +75,43 @@ class OffsetTest { fun offset_negativeValuesArePositionedCorrectly() { composeTestRule.setContent { Subspace { - SpatialPanel(SubspaceModifier.testTag("panel").offset(-20.dp, -20.dp, -20.dp)) { + SpatialPanel( + SubspaceModifier.testTag("panel").offset((-20).dp, (-20).dp, (-20).dp) + ) { + Text(text = "Panel") + } + } + } + + composeTestRule + .onSubspaceNodeWithTag("panel") + .assertPositionInRootIsEqualTo((-20).dp, (-20).dp, (-20).dp) + } + + @Test + fun offset_rtlLayoutIsPositionedCorrectly() { + composeTestRule.setContent { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + Subspace { + SpatialPanel(SubspaceModifier.testTag("panel").offset(20.dp, 20.dp, 20.dp)) { + Text(text = "Panel") + } + } + } + } + + composeTestRule + .onSubspaceNodeWithTag("panel") + .assertPositionInRootIsEqualTo((-20).dp, 20.dp, 20.dp) + } + + @Test + fun absoluteOffset_ltrLayoutIsPositionedCorrectly() { + composeTestRule.setContent { + Subspace { + SpatialPanel( + SubspaceModifier.testTag("panel").absoluteOffset(20.dp, 20.dp, 20.dp) + ) { Text(text = "Panel") } } @@ -80,7 +119,49 @@ class OffsetTest { composeTestRule .onSubspaceNodeWithTag("panel") - .assertPositionInRootIsEqualTo(-20.dp, -20.dp, -20.dp) + .assertPositionInRootIsEqualTo(20.dp, 20.dp, 20.dp) + } + + @Test + fun absoluteOffset_rtlLayoutIsPositionedCorrectly() { + composeTestRule.setContent { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + Subspace { + SpatialPanel( + SubspaceModifier.testTag("panel").absoluteOffset(20.dp, 20.dp, 20.dp) + ) { + Text(text = "Panel") + } + } + } + } + + composeTestRule + .onSubspaceNodeWithTag("panel") + .assertPositionInRootIsEqualTo(20.dp, 20.dp, 20.dp) + } + + @Test + fun offset_and_absoluteOffset_combined_rtlLayoutIsPositionedCorrectly() { + composeTestRule.setContent { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + Subspace { + SpatialPanel( + SubspaceModifier.testTag("panel") + .offset(20.dp, 20.dp, 20.dp) + .absoluteOffset(10.dp, 10.dp, 10.dp) + ) { + Text(text = "Panel") + } + } + } + } + + // Offset with RTL moves the panel -20 dp and absoluteOffset with RTL moves the panel 10 dp. + // Final x = -20 + 10 = -10 dp + composeTestRule + .onSubspaceNodeWithTag("panel") + .assertPositionInRootIsEqualTo((-10).dp, 30.dp, 30.dp) } @Test @@ -128,7 +209,7 @@ class OffsetTest { composeTestRule .onSubspaceNodeWithTag("panel1") - .assertPositionInRootIsEqualTo(-240.dp, 10.dp, 10.dp) // x=-(1000/2/2) + 10 + .assertPositionInRootIsEqualTo((-240).dp, 10.dp, 10.dp) // x=-(1000/2/2) + 10 .assertPositionIsEqualTo(10.dp, 10.dp, 10.dp) composeTestRule @@ -143,10 +224,7 @@ class OffsetTest { Subspace { var offsetX by remember { mutableStateOf(0.dp) } SpatialPanel(SubspaceModifier.testTag("panel").offset(x = offsetX)) { - Button( - modifier = Modifier.testTag("button"), - onClick = { offsetX = offsetX + 10.dp }, - ) { + Button(modifier = Modifier.testTag("button"), onClick = { offsetX += 10.dp }) { Text(text = "Click to change offset") } } From 06c7c173c7ef4de1cee035063cd2a19eb0b352a9 Mon Sep 17 00:00:00 2001 From: Yashwanth Gajji Date: Tue, 30 Dec 2025 15:23:41 -0800 Subject: [PATCH 15/19] Making SpatialRow inline function Relnote: "Making SpatialRow an inline function" Bug: 469149576 Test: N/A Change-Id: Ia2f20b8af183cf5064676fb597ed587cba240266 --- xr/compose/compose/api/current.txt | 4 +- xr/compose/compose/api/restricted_current.txt | 14 ++- .../xr/compose/subspace/SpatialColumn.kt | 1 - .../xr/compose/subspace/SpatialRow.kt | 96 ++++++++++--------- 4 files changed, 62 insertions(+), 53 deletions(-) diff --git a/xr/compose/compose/api/current.txt b/xr/compose/compose/api/current.txt index 18f344f76a2db..f1088a41b721a 100644 --- a/xr/compose/compose/api/current.txt +++ b/xr/compose/compose/api/current.txt @@ -361,9 +361,9 @@ package androidx.xr.compose.subspace { } public final class SpatialRowKt { - method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialCurvedRow(optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier, optional androidx.xr.compose.subspace.layout.SpatialAlignment alignment, optional androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.unit.Dp curveRadius, kotlin.jvm.functions.Function1 content); + method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static inline void SpatialCurvedRow(optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier, optional androidx.xr.compose.subspace.layout.SpatialAlignment alignment, optional androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.unit.Dp curveRadius, kotlin.jvm.functions.Function1 content); method @BytecodeOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialCurvedRow-hGBTI10(androidx.xr.compose.subspace.layout.SubspaceModifier?, androidx.xr.compose.subspace.layout.SpatialAlignment?, androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal?, float, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); - method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialRow(optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier, optional androidx.xr.compose.subspace.layout.SpatialAlignment alignment, optional androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal horizontalArrangement, kotlin.jvm.functions.Function1 content); + method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static inline void SpatialRow(optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier, optional androidx.xr.compose.subspace.layout.SpatialAlignment alignment, optional androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal horizontalArrangement, kotlin.jvm.functions.Function1 content); method @BytecodeOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialRow(androidx.xr.compose.subspace.layout.SubspaceModifier?, androidx.xr.compose.subspace.layout.SpatialAlignment?, androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal?, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); } diff --git a/xr/compose/compose/api/restricted_current.txt b/xr/compose/compose/api/restricted_current.txt index d9417cac065b4..90b9aef821098 100644 --- a/xr/compose/compose/api/restricted_current.txt +++ b/xr/compose/compose/api/restricted_current.txt @@ -316,10 +316,8 @@ package androidx.xr.compose.subspace { public final class SpatialColumnKt { method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static inline void SpatialColumn(optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier, optional androidx.xr.compose.subspace.layout.SpatialAlignment alignment, optional androidx.xr.compose.subspace.layout.SpatialArrangement.Vertical verticalArrangement, kotlin.jvm.functions.Function1 content); method @BytecodeOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialColumn(androidx.xr.compose.subspace.layout.SubspaceModifier?, androidx.xr.compose.subspace.layout.SpatialAlignment?, androidx.xr.compose.subspace.layout.SpatialArrangement.Vertical?, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); - method @InaccessibleFromKotlin @kotlin.PublishedApi internal static androidx.xr.compose.subspace.layout.SubspaceMeasurePolicy getDefaultSpatialColumnMeasurePolicy(); method @KotlinOnly @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.xr.compose.subspace.layout.SubspaceMeasurePolicy spatialColumnMeasurePolicy(androidx.xr.compose.subspace.layout.SpatialAlignment alignment, androidx.xr.compose.subspace.layout.SpatialArrangement.Vertical verticalArrangement); method @BytecodeOnly @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.xr.compose.subspace.layout.SubspaceMeasurePolicy spatialColumnMeasurePolicy(androidx.xr.compose.subspace.layout.SpatialAlignment, androidx.xr.compose.subspace.layout.SpatialArrangement.Vertical, androidx.compose.runtime.Composer?, int); - property @kotlin.PublishedApi internal static androidx.xr.compose.subspace.layout.SubspaceMeasurePolicy DefaultSpatialColumnMeasurePolicy; } @androidx.compose.foundation.layout.LayoutScopeMarker public interface SpatialColumnScope { @@ -381,10 +379,12 @@ package androidx.xr.compose.subspace { } public final class SpatialRowKt { - method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialCurvedRow(optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier, optional androidx.xr.compose.subspace.layout.SpatialAlignment alignment, optional androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.unit.Dp curveRadius, kotlin.jvm.functions.Function1 content); + method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static inline void SpatialCurvedRow(optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier, optional androidx.xr.compose.subspace.layout.SpatialAlignment alignment, optional androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.unit.Dp curveRadius, kotlin.jvm.functions.Function1 content); method @BytecodeOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialCurvedRow-hGBTI10(androidx.xr.compose.subspace.layout.SubspaceModifier?, androidx.xr.compose.subspace.layout.SpatialAlignment?, androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal?, float, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); - method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialRow(optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier, optional androidx.xr.compose.subspace.layout.SpatialAlignment alignment, optional androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal horizontalArrangement, kotlin.jvm.functions.Function1 content); + method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static inline void SpatialRow(optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier, optional androidx.xr.compose.subspace.layout.SpatialAlignment alignment, optional androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal horizontalArrangement, kotlin.jvm.functions.Function1 content); method @BytecodeOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialRow(androidx.xr.compose.subspace.layout.SubspaceModifier?, androidx.xr.compose.subspace.layout.SpatialAlignment?, androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal?, kotlin.jvm.functions.Function3, androidx.compose.runtime.Composer?, int, int); + method @KotlinOnly @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.xr.compose.subspace.layout.SubspaceMeasurePolicy spatialRowMeasurePolicy(androidx.compose.ui.unit.Dp curveRadius, androidx.xr.compose.subspace.layout.SpatialAlignment alignment, androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal horizontalArrangement); + method @BytecodeOnly @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.xr.compose.subspace.layout.SubspaceMeasurePolicy spatialRowMeasurePolicy--orJrPs(float, androidx.xr.compose.subspace.layout.SpatialAlignment, androidx.xr.compose.subspace.layout.SpatialArrangement.Horizontal, androidx.compose.runtime.Composer?, int); } @androidx.compose.foundation.layout.LayoutScopeMarker public interface SpatialRowScope { @@ -394,6 +394,12 @@ package androidx.xr.compose.subspace { method @BytecodeOnly public static androidx.xr.compose.subspace.layout.SubspaceModifier! weight$default(androidx.xr.compose.subspace.SpatialRowScope!, androidx.xr.compose.subspace.layout.SubspaceModifier!, float, boolean, int, Object!); } + @kotlin.PublishedApi internal final class SpatialRowScopeInstance implements androidx.xr.compose.subspace.SpatialRowScope { + method public androidx.xr.compose.subspace.layout.SubspaceModifier align(androidx.xr.compose.subspace.layout.SubspaceModifier, androidx.xr.compose.subspace.layout.SpatialAlignment.Depth alignment); + method public androidx.xr.compose.subspace.layout.SubspaceModifier align(androidx.xr.compose.subspace.layout.SubspaceModifier, androidx.xr.compose.subspace.layout.SpatialAlignment.Vertical alignment); + method public androidx.xr.compose.subspace.layout.SubspaceModifier weight(androidx.xr.compose.subspace.layout.SubspaceModifier, float weight, boolean fill); + } + public final class SpatialSpacerKt { method @KotlinOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialSpacer(optional androidx.xr.compose.subspace.layout.SubspaceModifier modifier); method @BytecodeOnly @androidx.compose.runtime.Composable @androidx.xr.compose.subspace.SubspaceComposable public static void SpatialSpacer(androidx.xr.compose.subspace.layout.SubspaceModifier?, androidx.compose.runtime.Composer?, int, int); diff --git a/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialColumn.kt b/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialColumn.kt index 7f0b2c3b0e876..0a56635a02f61 100644 --- a/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialColumn.kt +++ b/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialColumn.kt @@ -66,7 +66,6 @@ public inline fun SpatialColumn( ) } -@PublishedApi internal val DefaultSpatialColumnMeasurePolicy: SubspaceMeasurePolicy = SpatialColumnMeasurePolicy( alignment = SpatialAlignment.Center, diff --git a/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialRow.kt b/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialRow.kt index f10e6cb0596f1..10db654053fb0 100644 --- a/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialRow.kt +++ b/xr/compose/compose/src/main/kotlin/androidx/xr/compose/subspace/SpatialRow.kt @@ -24,8 +24,6 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastRoundToInt -import androidx.xr.compose.platform.LocalSession -import androidx.xr.compose.subspace.layout.CoreGroupEntity import androidx.xr.compose.subspace.layout.SpatialAlignment import androidx.xr.compose.subspace.layout.SpatialArrangement import androidx.xr.compose.subspace.layout.SubspaceLayout @@ -40,7 +38,6 @@ import androidx.xr.compose.unit.VolumeConstraints import androidx.xr.runtime.math.Pose import androidx.xr.runtime.math.Quaternion import androidx.xr.runtime.math.Vector3 -import androidx.xr.scenecore.GroupEntity import kotlin.math.cos import kotlin.math.sin @@ -55,13 +52,19 @@ import kotlin.math.sin */ @Composable @SubspaceComposable -public fun SpatialRow( +public inline fun SpatialRow( modifier: SubspaceModifier = SubspaceModifier, alignment: SpatialAlignment = SpatialAlignment.Center, horizontalArrangement: SpatialArrangement.Horizontal = SpatialArrangement.Center, - content: @Composable @SubspaceComposable SpatialRowScope.() -> Unit, + crossinline content: @Composable @SubspaceComposable SpatialRowScope.() -> Unit, ) { - SpatialRow(modifier, alignment, horizontalArrangement, Dp.Infinity, content) + SpatialCurvedRow( + modifier = modifier, + alignment = alignment, + horizontalArrangement = horizontalArrangement, + curveRadius = Dp.Infinity, + content = content, + ) } /** @@ -79,58 +82,58 @@ public fun SpatialRow( */ @Composable @SubspaceComposable -public fun SpatialCurvedRow( +public inline fun SpatialCurvedRow( modifier: SubspaceModifier = SubspaceModifier, alignment: SpatialAlignment = SpatialAlignment.Center, horizontalArrangement: SpatialArrangement.Horizontal = SpatialArrangement.Center, curveRadius: Dp = SpatialCurvedRowDefaults.curveRadius, - content: @Composable @SubspaceComposable SpatialRowScope.() -> Unit, -) { - SpatialRow(modifier, alignment, horizontalArrangement, curveRadius, content) -} - -/** - * A layout composable that arranges its children in a horizontal sequence. For arranging children - * vertically, see [SpatialColumn]. - * - * @param modifier Appearance modifiers to apply to this Composable. - * @param alignment The default alignment for child elements within the row. - * @param horizontalArrangement The horizontal arrangement of the children. - * @param curveRadius Defines the curve of the row by specifying its radius in Dp. A larger radius - * creates a gentler curve (less curvature), while a smaller positive radius results in a sharper - * curve (more curvature). Using [Dp.Infinity] or a non-positive value (zero or negative) makes - * the row straight. When curved, row items are angled to follow the curve's path. This value is - * the radial distance in the polar coordinate system. - * @param content The composable content to be laid out horizontally in the row. - */ -@Composable -@SubspaceComposable -private fun SpatialRow( - modifier: SubspaceModifier, - alignment: SpatialAlignment, - horizontalArrangement: SpatialArrangement.Horizontal, - curveRadius: Dp, - content: @Composable @SubspaceComposable SpatialRowScope.() -> Unit, + crossinline content: @Composable @SubspaceComposable SpatialRowScope.() -> Unit, ) { - val session = checkNotNull(LocalSession.current) { "session must be initialized" } - - val coreGroupEntity = remember { - CoreGroupEntity(GroupEntity.create(session, name = "SpatialRow", pose = Pose.Identity)) - } + val measurePolicy = + spatialRowMeasurePolicy( + curveRadius = if (curveRadius > 0.dp) curveRadius else Dp.Infinity, + alignment = alignment, + horizontalArrangement = horizontalArrangement, + ) SubspaceLayout( modifier = modifier, content = { SpatialRowScopeInstance.content() }, - coreEntity = coreGroupEntity, - measurePolicy = - SpatialRowMeasurePolicy( - if (curveRadius > 0.dp) curveRadius else Dp.Infinity, - alignment, - horizontalArrangement, - ), + coreEntityName = "SpatialRow", + measurePolicy = measurePolicy, ) } +internal val DefaultSpatialRowMeasurePolicy: SubspaceMeasurePolicy = + SpatialRowMeasurePolicy( + curveRadius = Dp.Infinity, + alignment = SpatialAlignment.Center, + horizontalArrangement = SpatialArrangement.Center, + ) + +@PublishedApi +@Composable +internal fun spatialRowMeasurePolicy( + curveRadius: Dp, + alignment: SpatialAlignment, + horizontalArrangement: SpatialArrangement.Horizontal, +): SubspaceMeasurePolicy = + if ( + curveRadius == Dp.Infinity && + alignment == SpatialAlignment.Center && + horizontalArrangement == SpatialArrangement.Center + ) { + DefaultSpatialRowMeasurePolicy + } else { + remember(curveRadius, alignment, horizontalArrangement) { + SpatialRowMeasurePolicy( + curveRadius = curveRadius, + alignment = alignment, + horizontalArrangement = horizontalArrangement, + ) + } + } + /** * Measure policy for [SpatialRow] and [SpatialCurvedRow] layouts. Handles the measurement and * placement of children in a horizontal sequence, optionally along a curve. @@ -379,6 +382,7 @@ public object SpatialCurvedRowDefaults { } /** Default implementation of the [SpatialRowScope] interface. */ +@PublishedApi internal object SpatialRowScopeInstance : SpatialRowScope { override fun SubspaceModifier.weight(weight: Float, fill: Boolean): SubspaceModifier { require(weight > 0.0) { "invalid weight $weight; must be greater than zero" } From 654945eb08b1ecc8c71165b7b7f0c194b16c86c4 Mon Sep 17 00:00:00 2001 From: Ralston Da Silva Date: Wed, 14 Jan 2026 13:52:42 -0800 Subject: [PATCH 16/19] Adding merged baseline profiles for datastore We added baseline profiles in aosp/3867894 and aosp/3891625 But these needed to be merged into a single baseline profile due to b/469127532 This CL adds merged baseline profiles for datastore so that we can publish datastore with baseline profiles. These merged baseline profiles should be removed once b/469127532 is fixed. Bug: 469127532 Test: Verified that the generated aar files have baseline profiles Change-Id: I9be890ef458f476aa6911230fa013f7db0d3c0ea --- .../baselineProfiles/baseline-prof.txt | 437 ++++++++++ .../baselineProfiles/baseline-prof.txt | 6 + .../baselineProfiles/baseline-prof.txt | 785 ++++++++++++++++++ .../baselineProfiles/baseline-prof.txt | 109 +++ .../baselineProfiles/baseline-prof.txt | 92 ++ 5 files changed, 1429 insertions(+) create mode 100644 datastore/datastore-core/src/androidMain/baselineProfiles/baseline-prof.txt create mode 100644 datastore/datastore-core/src/jvmAndAndroidMain/baselineProfiles/baseline-prof.txt create mode 100644 datastore/datastore-preferences-core/src/jvmAndAndroidMain/baselineProfiles/baseline-prof.txt create mode 100644 datastore/datastore-preferences/src/androidMain/baselineProfiles/baseline-prof.txt create mode 100644 datastore/datastore/src/androidMain/baselineProfiles/baseline-prof.txt diff --git a/datastore/datastore-core/src/androidMain/baselineProfiles/baseline-prof.txt b/datastore/datastore-core/src/androidMain/baselineProfiles/baseline-prof.txt new file mode 100644 index 0000000000000..caa17e9c58c12 --- /dev/null +++ b/datastore/datastore-core/src/androidMain/baselineProfiles/baseline-prof.txt @@ -0,0 +1,437 @@ +# TODO(b/469127532): Remove this merged baseline profile once AGP adds support for multiple +# baseline profile files per baselineProfiles directory. + +HSPLandroidx/datastore/core/Api26Impl;->()V +HSPLandroidx/datastore/core/Api26Impl;->()V +HSPLandroidx/datastore/core/Api26Impl;->move(Ljava/io/File;Ljava/io/File;)Z +HSPLandroidx/datastore/core/AtomicInt;->(I)V +HSPLandroidx/datastore/core/AtomicInt;->decrementAndGet()I +HSPLandroidx/datastore/core/AtomicInt;->get()I +HSPLandroidx/datastore/core/AtomicInt;->getAndIncrement()I +HSPLandroidx/datastore/core/AtomicInt;->incrementAndGet()I +HSPLandroidx/datastore/core/Data;->(Ljava/lang/Object;II)V +HSPLandroidx/datastore/core/Data;->checkHashCode()V +HSPLandroidx/datastore/core/Data;->getValue()Ljava/lang/Object; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$getInitializer$1;->(Ljava/util/List;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$getInitializer$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$getInitializer$1;->invoke(Landroidx/datastore/core/InitializerApi;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$getInitializer$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$getInitializer$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$runMigrations$1;->(Landroidx/datastore/core/DataMigrationInitializer$Companion;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$runMigrations$2;->(Ljava/util/List;Ljava/util/List;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$runMigrations$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$runMigrations$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$runMigrations$2;->invoke(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion$runMigrations$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion;->()V +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion;->access$runMigrations(Landroidx/datastore/core/DataMigrationInitializer$Companion;Ljava/util/List;Landroidx/datastore/core/InitializerApi;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion;->getInitializer(Ljava/util/List;)Lkotlin/jvm/functions/Function2; +HSPLandroidx/datastore/core/DataMigrationInitializer$Companion;->runMigrations(Ljava/util/List;Landroidx/datastore/core/InitializerApi;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataMigrationInitializer;->()V +HSPLandroidx/datastore/core/DataStoreFactory;->()V +HSPLandroidx/datastore/core/DataStoreFactory;->()V +HSPLandroidx/datastore/core/DataStoreFactory;->create(Landroidx/datastore/core/Storage;Landroidx/datastore/core/handlers/ReplaceFileCorruptionHandler;Ljava/util/List;Lkotlinx/coroutines/CoroutineScope;)Landroidx/datastore/core/DataStore; +HSPLandroidx/datastore/core/DataStoreImpl$$ExternalSyntheticLambda0;->(Landroidx/datastore/core/DataStoreImpl;)V +HSPLandroidx/datastore/core/DataStoreImpl$$ExternalSyntheticLambda0;->invoke()Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$$ExternalSyntheticLambda1;->(Landroidx/datastore/core/DataStoreImpl;)V +HSPLandroidx/datastore/core/DataStoreImpl$$ExternalSyntheticLambda1;->invoke()Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$$ExternalSyntheticLambda2;->(Landroidx/datastore/core/DataStoreImpl;)V +HSPLandroidx/datastore/core/DataStoreImpl$$ExternalSyntheticLambda3;->()V +HSPLandroidx/datastore/core/DataStoreImpl$Companion;->()V +HSPLandroidx/datastore/core/DataStoreImpl$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$1;->(Landroidx/datastore/core/DataStoreImpl$InitDataStore;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1$api$1$updateData$1;->(Landroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1$api$1;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1$api$1;->(Lkotlinx/coroutines/sync/Mutex;Lkotlin/jvm/internal/Ref$BooleanRef;Lkotlin/jvm/internal/Ref$ObjectRef;Landroidx/datastore/core/DataStoreImpl;)V +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1$api$1;->updateData(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1;->(Landroidx/datastore/core/DataStoreImpl;Landroidx/datastore/core/DataStoreImpl$InitDataStore;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1;->create(Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1;->invoke(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore;->(Landroidx/datastore/core/DataStoreImpl;Ljava/util/List;)V +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore;->access$getInitTasks$p(Landroidx/datastore/core/DataStoreImpl$InitDataStore;)Ljava/util/List; +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore;->access$setInitTasks$p(Landroidx/datastore/core/DataStoreImpl$InitDataStore;Ljava/util/List;)V +HSPLandroidx/datastore/core/DataStoreImpl$InitDataStore;->doRun(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$data$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$1;->invoke(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$2;->(Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$data$1$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$2;->invoke(Landroidx/datastore/core/State;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$3;->(Landroidx/datastore/core/State;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$data$1$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$3;->invoke(Landroidx/datastore/core/State;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$5;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1$2$1;->(Landroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1$2;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1$2;->(Lkotlinx/coroutines/flow/FlowCollector;)V +HSPLandroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1$2;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1;->(Lkotlinx/coroutines/flow/Flow;)V +HSPLandroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$data$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$data$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1;->invoke(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$data$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$handleUpdate$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$handleUpdate$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$handleUpdate$2$1;->(Landroidx/datastore/core/DataStoreImpl;Landroidx/datastore/core/Message$Update;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$handleUpdate$2$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$handleUpdate$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$handleUpdate$2$1;->invoke(Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$handleUpdate$2$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$incrementCollector$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$incrementCollector$2$1$1;->(Landroidx/datastore/core/DataStoreImpl;)V +HSPLandroidx/datastore/core/DataStoreImpl$incrementCollector$2$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$incrementCollector$2$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$incrementCollector$2$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$readAndInitOrPropagateAndThrowFailure$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$readAndInitOrPropagateAndThrowFailure$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$readDataAndUpdateCache$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$readDataOrHandleCorruption$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$readState$2;->(Landroidx/datastore/core/DataStoreImpl;ZLkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$readState$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$readState$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2$newData$1;->(Lkotlin/jvm/functions/Function2;Landroidx/datastore/core/Data;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2$newData$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2$newData$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2;->create(Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2;->invoke(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$updateData$2;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$updateData$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$updateData$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$updateData$2;->invoke(Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$updateData$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$writeActor$3;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$writeActor$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$writeActor$3;->invoke(Landroidx/datastore/core/Message$Update;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$writeActor$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$writeActor$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$writeData$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$writeData$2;->(Lkotlin/jvm/internal/Ref$IntRef;Landroidx/datastore/core/DataStoreImpl;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/DataStoreImpl$writeData$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/DataStoreImpl$writeData$2;->invoke(Landroidx/datastore/core/WriteScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$writeData$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl$writeData$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->$r8$lambda$2NKMTsGrD22twvtEszaZakafv3g(Landroidx/datastore/core/DataStoreImpl;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/DataStoreImpl;->$r8$lambda$iRq06cf8rCKc8bSbBlIMwgbMVK4(Landroidx/datastore/core/DataStoreImpl;)Landroidx/datastore/core/StorageConnection; +HSPLandroidx/datastore/core/DataStoreImpl;->()V +HSPLandroidx/datastore/core/DataStoreImpl;->(Landroidx/datastore/core/Storage;Ljava/util/List;Landroidx/datastore/core/CorruptionHandler;Lkotlinx/coroutines/CoroutineScope;)V +HSPLandroidx/datastore/core/DataStoreImpl;->access$getCoordinator(Landroidx/datastore/core/DataStoreImpl;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/DataStoreImpl;->access$getInMemoryCache$p(Landroidx/datastore/core/DataStoreImpl;)Landroidx/datastore/core/DataStoreInMemoryCache; +HSPLandroidx/datastore/core/DataStoreImpl;->access$getReadAndInit$p(Landroidx/datastore/core/DataStoreImpl;)Landroidx/datastore/core/DataStoreImpl$InitDataStore; +HSPLandroidx/datastore/core/DataStoreImpl;->access$getWriteActor$p(Landroidx/datastore/core/DataStoreImpl;)Landroidx/datastore/core/SimpleActor; +HSPLandroidx/datastore/core/DataStoreImpl;->access$handleUpdate(Landroidx/datastore/core/DataStoreImpl;Landroidx/datastore/core/Message$Update;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->access$incrementCollector(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->access$readAndInitOrPropagateAndThrowFailure(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->access$readDataAndUpdateCache(Landroidx/datastore/core/DataStoreImpl;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->access$readDataOrHandleCorruption(Landroidx/datastore/core/DataStoreImpl;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->access$readState(Landroidx/datastore/core/DataStoreImpl;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->access$transformAndWrite(Landroidx/datastore/core/DataStoreImpl;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->coordinator_delegate$lambda$0(Landroidx/datastore/core/DataStoreImpl;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/DataStoreImpl;->getCoordinator()Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/DataStoreImpl;->getData()Lkotlinx/coroutines/flow/Flow; +HSPLandroidx/datastore/core/DataStoreImpl;->getStorageConnection$datastore_core()Landroidx/datastore/core/StorageConnection; +HSPLandroidx/datastore/core/DataStoreImpl;->handleUpdate(Landroidx/datastore/core/Message$Update;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->incrementCollector(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->readAndInitOrPropagateAndThrowFailure(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->readDataAndUpdateCache(ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->readDataFromFileOrDefault(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->readDataOrHandleCorruption(ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->readState(ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->storageConnectionDelegate$lambda$0(Landroidx/datastore/core/DataStoreImpl;)Landroidx/datastore/core/StorageConnection; +HSPLandroidx/datastore/core/DataStoreImpl;->transformAndWrite(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->updateData(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreImpl;->writeData$datastore_core(Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/DataStoreInMemoryCache;->()V +HSPLandroidx/datastore/core/DataStoreInMemoryCache;->getCurrentState()Landroidx/datastore/core/State; +HSPLandroidx/datastore/core/DataStoreInMemoryCache;->getFlow()Lkotlinx/coroutines/flow/Flow; +HSPLandroidx/datastore/core/DataStoreInMemoryCache;->tryUpdate(Landroidx/datastore/core/State;)Landroidx/datastore/core/State; +HSPLandroidx/datastore/core/FileMoves_androidKt;->atomicMoveTo(Ljava/io/File;Ljava/io/File;)Z +HSPLandroidx/datastore/core/FileReadScope$readData$2;->(Landroidx/datastore/core/FileReadScope;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/FileReadScope$readData$2;->create(Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/FileReadScope$readData$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileReadScope$readData$2;->invoke(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileReadScope$readData$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileReadScope;->(Ljava/io/File;Landroidx/datastore/core/Serializer;)V +HSPLandroidx/datastore/core/FileReadScope;->checkNotClosed()V +HSPLandroidx/datastore/core/FileReadScope;->close()V +HSPLandroidx/datastore/core/FileReadScope;->getFile()Ljava/io/File; +HSPLandroidx/datastore/core/FileReadScope;->getSerializer()Landroidx/datastore/core/Serializer; +HSPLandroidx/datastore/core/FileReadScope;->readData$suspendImpl(Landroidx/datastore/core/FileReadScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileReadScope;->readData(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileStorage$$ExternalSyntheticLambda0;->(Ljava/io/File;)V +HSPLandroidx/datastore/core/FileStorage$$ExternalSyntheticLambda1;->()V +HSPLandroidx/datastore/core/FileStorage$$ExternalSyntheticLambda1;->invoke(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileStorage$Companion;->()V +HSPLandroidx/datastore/core/FileStorage$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/FileStorage;->$r8$lambda$i_CTkxVTKhfAC0UyaWMuAs7ImrQ(Ljava/io/File;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/FileStorage;->()V +HSPLandroidx/datastore/core/FileStorage;->(Landroidx/datastore/core/Serializer;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)V +HSPLandroidx/datastore/core/FileStorage;->(Landroidx/datastore/core/Serializer;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/FileStorage;->_init_$lambda$0(Ljava/io/File;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/FileStorage;->createConnection()Landroidx/datastore/core/StorageConnection; +HSPLandroidx/datastore/core/FileStorageConnection$readScope$1;->(Landroidx/datastore/core/FileStorageConnection;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/FileStorageConnection$writeScope$1;->(Landroidx/datastore/core/FileStorageConnection;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/FileStorageConnection;->(Ljava/io/File;Landroidx/datastore/core/Serializer;Landroidx/datastore/core/InterProcessCoordinator;Lkotlin/jvm/functions/Function0;)V +HSPLandroidx/datastore/core/FileStorageConnection;->checkNotClosed()V +HSPLandroidx/datastore/core/FileStorageConnection;->createParentDirectories(Ljava/io/File;)V +HSPLandroidx/datastore/core/FileStorageConnection;->getCoordinator()Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/FileStorageConnection;->readScope(Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileStorageConnection;->writeScope(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileStorageKt$runFileDiagnosticsIfNotCorruption$1;->(Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/FileStorageKt;->access$runFileDiagnosticsIfNotCorruption(Ljava/io/File;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileStorageKt;->runFileDiagnosticsIfNotCorruption(Ljava/io/File;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileWriteScope$writeData$2;->(Landroidx/datastore/core/FileWriteScope;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/FileWriteScope$writeData$2;->create(Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/FileWriteScope$writeData$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileWriteScope$writeData$2;->invoke(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileWriteScope$writeData$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/FileWriteScope;->(Ljava/io/File;Landroidx/datastore/core/Serializer;)V +HSPLandroidx/datastore/core/FileWriteScope;->writeData(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/InterProcessCoordinatorKt;->createSingleProcessCoordinator(Ljava/lang/String;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/InterProcessCoordinator_jvmKt;->createSingleProcessCoordinator(Ljava/io/File;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/Message$Update;->(Lkotlin/jvm/functions/Function2;Lkotlinx/coroutines/CompletableDeferred;Landroidx/datastore/core/State;Lkotlin/coroutines/CoroutineContext;)V +HSPLandroidx/datastore/core/Message$Update;->getAck()Lkotlinx/coroutines/CompletableDeferred; +HSPLandroidx/datastore/core/Message$Update;->getCallerContext()Lkotlin/coroutines/CoroutineContext; +HSPLandroidx/datastore/core/Message$Update;->getTransform()Lkotlin/jvm/functions/Function2; +HSPLandroidx/datastore/core/Message;->()V +HSPLandroidx/datastore/core/Message;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/NoValueDataState;->(I)V +HSPLandroidx/datastore/core/RunOnce$runIfNeeded$1;->(Landroidx/datastore/core/RunOnce;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/RunOnce$runIfNeeded$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/RunOnce;->()V +HSPLandroidx/datastore/core/RunOnce;->awaitComplete(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/RunOnce;->runIfNeeded(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/SimpleActor$$ExternalSyntheticLambda0;->(Lkotlin/jvm/functions/Function1;Landroidx/datastore/core/SimpleActor;Lkotlin/jvm/functions/Function2;)V +HSPLandroidx/datastore/core/SimpleActor$offer$2;->(Landroidx/datastore/core/SimpleActor;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/SimpleActor$offer$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/SimpleActor$offer$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/SimpleActor;->(Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V +HSPLandroidx/datastore/core/SimpleActor;->access$getConsumeMessage$p(Landroidx/datastore/core/SimpleActor;)Lkotlin/jvm/functions/Function2; +HSPLandroidx/datastore/core/SimpleActor;->access$getMessageQueue$p(Landroidx/datastore/core/SimpleActor;)Lkotlinx/coroutines/channels/Channel; +HSPLandroidx/datastore/core/SimpleActor;->access$getRemainingMessages$p(Landroidx/datastore/core/SimpleActor;)Landroidx/datastore/core/AtomicInt; +HSPLandroidx/datastore/core/SimpleActor;->access$getScope$p(Landroidx/datastore/core/SimpleActor;)Lkotlinx/coroutines/CoroutineScope; +HSPLandroidx/datastore/core/SimpleActor;->offer(Ljava/lang/Object;)V +HSPLandroidx/datastore/core/SingleProcessCoordinator$lock$1;->(Landroidx/datastore/core/SingleProcessCoordinator;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/SingleProcessCoordinator$lock$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/SingleProcessCoordinator$updateNotifications$1;->(Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/SingleProcessCoordinator$updateNotifications$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/core/SingleProcessCoordinator$updateNotifications$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/SingleProcessCoordinator$updateNotifications$1;->invoke(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/SingleProcessCoordinator$updateNotifications$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/SingleProcessCoordinator;->(Ljava/lang/String;)V +HSPLandroidx/datastore/core/SingleProcessCoordinator;->getUpdateNotifications()Lkotlinx/coroutines/flow/Flow; +HSPLandroidx/datastore/core/SingleProcessCoordinator;->getVersion(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/SingleProcessCoordinator;->incrementAndGetVersion(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/SingleProcessCoordinator;->lock(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/State;->(I)V +HSPLandroidx/datastore/core/State;->(ILkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/State;->getVersion()I +HSPLandroidx/datastore/core/StorageConnectionKt$readData$2;->(Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/StorageConnectionKt$readData$2;->invoke(Landroidx/datastore/core/ReadScope;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/StorageConnectionKt$readData$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/StorageConnectionKt$readData$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/StorageConnectionKt;->readData(Landroidx/datastore/core/StorageConnection;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/UnInitialized;->()V +HSPLandroidx/datastore/core/UnInitialized;->()V +HSPLandroidx/datastore/core/UncloseableOutputStream;->(Ljava/io/FileOutputStream;)V +HSPLandroidx/datastore/core/UncloseableOutputStream;->write([BII)V +HSPLandroidx/datastore/core/UpdatingDataContextElement$Companion$Key;->()V +HSPLandroidx/datastore/core/UpdatingDataContextElement$Companion$Key;->()V +HSPLandroidx/datastore/core/UpdatingDataContextElement$Companion;->()V +HSPLandroidx/datastore/core/UpdatingDataContextElement$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/UpdatingDataContextElement;->()V +HSPLandroidx/datastore/core/UpdatingDataContextElement;->(Landroidx/datastore/core/UpdatingDataContextElement;Landroidx/datastore/core/DataStoreImpl;)V +HSPLandroidx/datastore/core/UpdatingDataContextElement;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; +HSPLandroidx/datastore/core/UpdatingDataContextElement;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; +HSPLandroidx/datastore/core/UpdatingDataContextElement;->getKey()Lkotlin/coroutines/CoroutineContext$Key; +HSPLandroidx/datastore/core/UpdatingDataContextElement;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; +Landroidx/datastore/core/Api26Impl; +Landroidx/datastore/core/AtomicInt; +Landroidx/datastore/core/Closeable; +Landroidx/datastore/core/CorruptionException; +Landroidx/datastore/core/CorruptionHandler; +Landroidx/datastore/core/CurrentDataProviderStore; +Landroidx/datastore/core/Data; +Landroidx/datastore/core/DataMigrationInitializer$Companion$getInitializer$1; +Landroidx/datastore/core/DataMigrationInitializer$Companion$runMigrations$1; +Landroidx/datastore/core/DataMigrationInitializer$Companion$runMigrations$2; +Landroidx/datastore/core/DataMigrationInitializer$Companion; +Landroidx/datastore/core/DataMigrationInitializer; +Landroidx/datastore/core/DataStore; +Landroidx/datastore/core/DataStoreFactory; +Landroidx/datastore/core/DataStoreImpl$$ExternalSyntheticLambda0; +Landroidx/datastore/core/DataStoreImpl$$ExternalSyntheticLambda1; +Landroidx/datastore/core/DataStoreImpl$$ExternalSyntheticLambda2; +Landroidx/datastore/core/DataStoreImpl$$ExternalSyntheticLambda3; +Landroidx/datastore/core/DataStoreImpl$Companion; +Landroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$1; +Landroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1$api$1$updateData$1; +Landroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1$api$1; +Landroidx/datastore/core/DataStoreImpl$InitDataStore$doRun$initData$1; +Landroidx/datastore/core/DataStoreImpl$InitDataStore; +Landroidx/datastore/core/DataStoreImpl$data$1$1; +Landroidx/datastore/core/DataStoreImpl$data$1$2; +Landroidx/datastore/core/DataStoreImpl$data$1$3; +Landroidx/datastore/core/DataStoreImpl$data$1$5; +Landroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1$2$1; +Landroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1$2; +Landroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1; +Landroidx/datastore/core/DataStoreImpl$data$1; +Landroidx/datastore/core/DataStoreImpl$handleUpdate$1; +Landroidx/datastore/core/DataStoreImpl$handleUpdate$2$1; +Landroidx/datastore/core/DataStoreImpl$incrementCollector$1; +Landroidx/datastore/core/DataStoreImpl$incrementCollector$2$1$1; +Landroidx/datastore/core/DataStoreImpl$incrementCollector$2$1; +Landroidx/datastore/core/DataStoreImpl$readAndInitOrPropagateAndThrowFailure$1; +Landroidx/datastore/core/DataStoreImpl$readDataAndUpdateCache$1; +Landroidx/datastore/core/DataStoreImpl$readDataOrHandleCorruption$1; +Landroidx/datastore/core/DataStoreImpl$readState$2; +Landroidx/datastore/core/DataStoreImpl$transformAndWrite$2$newData$1; +Landroidx/datastore/core/DataStoreImpl$transformAndWrite$2; +Landroidx/datastore/core/DataStoreImpl$updateData$2; +Landroidx/datastore/core/DataStoreImpl$writeActor$3; +Landroidx/datastore/core/DataStoreImpl$writeData$1; +Landroidx/datastore/core/DataStoreImpl$writeData$2; +Landroidx/datastore/core/DataStoreImpl; +Landroidx/datastore/core/DataStoreInMemoryCache; +Landroidx/datastore/core/FileMoves_androidKt; +Landroidx/datastore/core/FileReadScope$readData$2; +Landroidx/datastore/core/FileReadScope; +Landroidx/datastore/core/FileStorage$$ExternalSyntheticLambda0; +Landroidx/datastore/core/FileStorage$$ExternalSyntheticLambda1; +Landroidx/datastore/core/FileStorage$Companion; +Landroidx/datastore/core/FileStorage; +Landroidx/datastore/core/FileStorageConnection$readScope$1; +Landroidx/datastore/core/FileStorageConnection$writeScope$1; +Landroidx/datastore/core/FileStorageConnection; +Landroidx/datastore/core/FileStorageKt$runFileDiagnosticsIfNotCorruption$1; +Landroidx/datastore/core/FileStorageKt; +Landroidx/datastore/core/FileWriteScope$writeData$2; +Landroidx/datastore/core/FileWriteScope; +Landroidx/datastore/core/Final; +Landroidx/datastore/core/InitializerApi; +Landroidx/datastore/core/InterProcessCoordinator; +Landroidx/datastore/core/InterProcessCoordinatorKt; +Landroidx/datastore/core/InterProcessCoordinator_jvmKt; +Landroidx/datastore/core/Message$Update; +Landroidx/datastore/core/Message; +Landroidx/datastore/core/NoValueDataState; +Landroidx/datastore/core/ReadException; +Landroidx/datastore/core/ReadScope; +Landroidx/datastore/core/RunOnce$runIfNeeded$1; +Landroidx/datastore/core/RunOnce; +Landroidx/datastore/core/Serializer; +Landroidx/datastore/core/SimpleActor$$ExternalSyntheticLambda0; +Landroidx/datastore/core/SimpleActor$offer$2; +Landroidx/datastore/core/SimpleActor; +Landroidx/datastore/core/SingleProcessCoordinator$lock$1; +Landroidx/datastore/core/SingleProcessCoordinator$updateNotifications$1; +Landroidx/datastore/core/SingleProcessCoordinator; +Landroidx/datastore/core/State; +Landroidx/datastore/core/Storage; +Landroidx/datastore/core/StorageConnection; +Landroidx/datastore/core/StorageConnectionKt$readData$2; +Landroidx/datastore/core/StorageConnectionKt; +Landroidx/datastore/core/UnInitialized; +Landroidx/datastore/core/UncloseableOutputStream; +Landroidx/datastore/core/UpdatingDataContextElement$Companion$Key; +Landroidx/datastore/core/UpdatingDataContextElement$Companion; +Landroidx/datastore/core/UpdatingDataContextElement; +Landroidx/datastore/core/WriteScope; +PLandroidx/datastore/core/Api26Impl;->()V +PLandroidx/datastore/core/Api26Impl;->()V +PLandroidx/datastore/core/Api26Impl;->move(Ljava/io/File;Ljava/io/File;)Z +PLandroidx/datastore/core/AtomicInt;->decrementAndGet()I +PLandroidx/datastore/core/AtomicInt;->getAndIncrement()I +PLandroidx/datastore/core/AtomicInt;->incrementAndGet()I +PLandroidx/datastore/core/Data;->checkHashCode()V +PLandroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1$2$1;->(Landroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1$2;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/DataStoreImpl$data$1$invokeSuspend$$inlined$map$1$2;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$handleUpdate$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/DataStoreImpl$handleUpdate$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$handleUpdate$2$1;->(Landroidx/datastore/core/DataStoreImpl;Landroidx/datastore/core/Message$Update;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/DataStoreImpl$handleUpdate$2$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +PLandroidx/datastore/core/DataStoreImpl$handleUpdate$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$handleUpdate$2$1;->invoke(Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$handleUpdate$2$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2$newData$1;->(Lkotlin/jvm/functions/Function2;Landroidx/datastore/core/Data;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2$newData$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +PLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2$newData$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2;->create(Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +PLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2;->invoke(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$transformAndWrite$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$updateData$2;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/DataStoreImpl$updateData$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +PLandroidx/datastore/core/DataStoreImpl$updateData$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$updateData$2;->invoke(Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$updateData$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$writeActor$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +PLandroidx/datastore/core/DataStoreImpl$writeActor$3;->invoke(Landroidx/datastore/core/Message$Update;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$writeActor$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$writeActor$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$writeData$1;->(Landroidx/datastore/core/DataStoreImpl;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/DataStoreImpl$writeData$2;->(Lkotlin/jvm/internal/Ref$IntRef;Landroidx/datastore/core/DataStoreImpl;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/DataStoreImpl$writeData$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +PLandroidx/datastore/core/DataStoreImpl$writeData$2;->invoke(Landroidx/datastore/core/WriteScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$writeData$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl$writeData$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl;->access$getWriteActor$p(Landroidx/datastore/core/DataStoreImpl;)Landroidx/datastore/core/SimpleActor; +PLandroidx/datastore/core/DataStoreImpl;->access$handleUpdate(Landroidx/datastore/core/DataStoreImpl;Landroidx/datastore/core/Message$Update;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl;->access$transformAndWrite(Landroidx/datastore/core/DataStoreImpl;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl;->handleUpdate(Landroidx/datastore/core/Message$Update;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl;->transformAndWrite(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl;->updateData(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/DataStoreImpl;->writeData$datastore_core(Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/FileMoves_androidKt;->atomicMoveTo(Ljava/io/File;Ljava/io/File;)Z +PLandroidx/datastore/core/FileStorageConnection$writeScope$1;->(Landroidx/datastore/core/FileStorageConnection;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/FileStorageConnection;->createParentDirectories(Ljava/io/File;)V +PLandroidx/datastore/core/FileStorageConnection;->writeScope(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/FileWriteScope$writeData$2;->(Landroidx/datastore/core/FileWriteScope;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/FileWriteScope$writeData$2;->create(Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +PLandroidx/datastore/core/FileWriteScope$writeData$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/FileWriteScope$writeData$2;->invoke(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/FileWriteScope$writeData$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/FileWriteScope;->(Ljava/io/File;Landroidx/datastore/core/Serializer;)V +PLandroidx/datastore/core/FileWriteScope;->writeData(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/Message$Update;->(Lkotlin/jvm/functions/Function2;Lkotlinx/coroutines/CompletableDeferred;Landroidx/datastore/core/State;Lkotlin/coroutines/CoroutineContext;)V +PLandroidx/datastore/core/Message$Update;->getAck()Lkotlinx/coroutines/CompletableDeferred; +PLandroidx/datastore/core/Message$Update;->getCallerContext()Lkotlin/coroutines/CoroutineContext; +PLandroidx/datastore/core/Message$Update;->getTransform()Lkotlin/jvm/functions/Function2; +PLandroidx/datastore/core/Message;->()V +PLandroidx/datastore/core/Message;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +PLandroidx/datastore/core/NoValueDataState;->(I)V +PLandroidx/datastore/core/SimpleActor$offer$2;->(Landroidx/datastore/core/SimpleActor;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/SimpleActor$offer$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +PLandroidx/datastore/core/SimpleActor$offer$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/SimpleActor;->access$getConsumeMessage$p(Landroidx/datastore/core/SimpleActor;)Lkotlin/jvm/functions/Function2; +PLandroidx/datastore/core/SimpleActor;->access$getMessageQueue$p(Landroidx/datastore/core/SimpleActor;)Lkotlinx/coroutines/channels/Channel; +PLandroidx/datastore/core/SimpleActor;->access$getRemainingMessages$p(Landroidx/datastore/core/SimpleActor;)Landroidx/datastore/core/AtomicInt; +PLandroidx/datastore/core/SimpleActor;->access$getScope$p(Landroidx/datastore/core/SimpleActor;)Lkotlinx/coroutines/CoroutineScope; +PLandroidx/datastore/core/SimpleActor;->offer(Ljava/lang/Object;)V +PLandroidx/datastore/core/SingleProcessCoordinator$lock$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/core/SingleProcessCoordinator;->incrementAndGetVersion(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/UncloseableOutputStream;->(Ljava/io/FileOutputStream;)V +PLandroidx/datastore/core/UncloseableOutputStream;->write([BII)V +PLandroidx/datastore/core/UpdatingDataContextElement$Companion$Key;->()V +PLandroidx/datastore/core/UpdatingDataContextElement$Companion$Key;->()V +PLandroidx/datastore/core/UpdatingDataContextElement$Companion;->()V +PLandroidx/datastore/core/UpdatingDataContextElement$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +PLandroidx/datastore/core/UpdatingDataContextElement;->()V +PLandroidx/datastore/core/UpdatingDataContextElement;->(Landroidx/datastore/core/UpdatingDataContextElement;Landroidx/datastore/core/DataStoreImpl;)V +PLandroidx/datastore/core/UpdatingDataContextElement;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; +PLandroidx/datastore/core/UpdatingDataContextElement;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; +PLandroidx/datastore/core/UpdatingDataContextElement;->getKey()Lkotlin/coroutines/CoroutineContext$Key; +PLandroidx/datastore/core/UpdatingDataContextElement;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; diff --git a/datastore/datastore-core/src/jvmAndAndroidMain/baselineProfiles/baseline-prof.txt b/datastore/datastore-core/src/jvmAndAndroidMain/baselineProfiles/baseline-prof.txt new file mode 100644 index 0000000000000..76715a02fe203 --- /dev/null +++ b/datastore/datastore-core/src/jvmAndAndroidMain/baselineProfiles/baseline-prof.txt @@ -0,0 +1,6 @@ +# TODO(b/469127532): Remove this merged baseline profile once AGP adds support for multiple +# baseline profile files per baselineProfiles directory. + +HSPLandroidx/datastore/core/handlers/NoOpCorruptionHandler;->()V +Landroidx/datastore/core/handlers/NoOpCorruptionHandler; +Landroidx/datastore/core/handlers/ReplaceFileCorruptionHandler; diff --git a/datastore/datastore-preferences-core/src/jvmAndAndroidMain/baselineProfiles/baseline-prof.txt b/datastore/datastore-preferences-core/src/jvmAndAndroidMain/baselineProfiles/baseline-prof.txt new file mode 100644 index 0000000000000..9a28d32c129b1 --- /dev/null +++ b/datastore/datastore-preferences-core/src/jvmAndAndroidMain/baselineProfiles/baseline-prof.txt @@ -0,0 +1,785 @@ +# TODO(b/469127532): Remove this merged baseline profile once AGP adds support for multiple +# baseline profile files per baselineProfiles directory. + +HSPLandroidx/datastore/core/okio/AtomicBoolean;->(Z)V +HSPLandroidx/datastore/core/okio/AtomicBoolean;->get()Z +HSPLandroidx/datastore/core/okio/AtomicBoolean;->set(Z)V +HSPLandroidx/datastore/core/okio/OkioReadScope$readData$1;->(Landroidx/datastore/core/okio/OkioReadScope;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/okio/OkioReadScope;->(Lokio/FileSystem;Lokio/Path;Landroidx/datastore/core/okio/OkioSerializer;)V +HSPLandroidx/datastore/core/okio/OkioReadScope;->checkClose()V +HSPLandroidx/datastore/core/okio/OkioReadScope;->close()V +HSPLandroidx/datastore/core/okio/OkioReadScope;->getFileSystem()Lokio/FileSystem; +HSPLandroidx/datastore/core/okio/OkioReadScope;->getPath()Lokio/Path; +HSPLandroidx/datastore/core/okio/OkioReadScope;->getSerializer()Landroidx/datastore/core/okio/OkioSerializer; +HSPLandroidx/datastore/core/okio/OkioReadScope;->readData$suspendImpl(Landroidx/datastore/core/okio/OkioReadScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioReadScope;->readData(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda0;->(Landroidx/datastore/core/okio/OkioStorage;)V +HSPLandroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda1;->()V +HSPLandroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda2;->(Landroidx/datastore/core/okio/OkioStorage;)V +HSPLandroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda2;->invoke()Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioStorage$Companion;->()V +HSPLandroidx/datastore/core/okio/OkioStorage$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/okio/OkioStorage;->$r8$lambda$ngX1sQHRBFS9is12Tq2RMloe3b8(Landroidx/datastore/core/okio/OkioStorage;)Lokio/Path; +HSPLandroidx/datastore/core/okio/OkioStorage;->$r8$lambda$zaSHyDobNeO3yk4Wyl_gDVAF7jA(Lokio/Path;Lokio/FileSystem;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/okio/OkioStorage;->()V +HSPLandroidx/datastore/core/okio/OkioStorage;->(Lokio/FileSystem;Landroidx/datastore/core/okio/OkioSerializer;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;)V +HSPLandroidx/datastore/core/okio/OkioStorage;->(Lokio/FileSystem;Landroidx/datastore/core/okio/OkioSerializer;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/okio/OkioStorage;->_init_$lambda$0(Lokio/Path;Lokio/FileSystem;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/okio/OkioStorage;->canonicalPath_delegate$lambda$0(Landroidx/datastore/core/okio/OkioStorage;)Lokio/Path; +HSPLandroidx/datastore/core/okio/OkioStorage;->createConnection()Landroidx/datastore/core/StorageConnection; +HSPLandroidx/datastore/core/okio/OkioStorage;->getCanonicalPath()Lokio/Path; +HSPLandroidx/datastore/core/okio/OkioStorageConnection$readScope$1;->(Landroidx/datastore/core/okio/OkioStorageConnection;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/okio/OkioStorageConnection$writeScope$1;->(Landroidx/datastore/core/okio/OkioStorageConnection;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/okio/OkioStorageConnection;->(Lokio/FileSystem;Lokio/Path;Landroidx/datastore/core/okio/OkioSerializer;Landroidx/datastore/core/InterProcessCoordinator;Lkotlin/jvm/functions/Function0;)V +HSPLandroidx/datastore/core/okio/OkioStorageConnection;->checkNotClosed()V +HSPLandroidx/datastore/core/okio/OkioStorageConnection;->getCoordinator()Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/okio/OkioStorageConnection;->readScope(Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioStorageConnection;->writeScope(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioStorageKt;->createSingleProcessCoordinator(Lokio/Path;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/okio/OkioWriteScope$writeData$1;->(Landroidx/datastore/core/okio/OkioWriteScope;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/okio/OkioWriteScope;->(Lokio/FileSystem;Lokio/Path;Landroidx/datastore/core/okio/OkioSerializer;)V +HSPLandroidx/datastore/core/okio/OkioWriteScope;->writeData(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/Synchronizer;->()V +HSPLandroidx/datastore/preferences/PreferencesMapCompat$Companion;->()V +HSPLandroidx/datastore/preferences/PreferencesMapCompat$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/preferences/PreferencesMapCompat$Companion;->readFrom(Ljava/io/InputStream;)Landroidx/datastore/preferences/PreferencesProto$PreferenceMap; +HSPLandroidx/datastore/preferences/PreferencesMapCompat;->()V +HSPLandroidx/datastore/preferences/core/Actual_jvmAndroidKt;->immutableMap(Ljava/util/Map;)Ljava/util/Map; +HSPLandroidx/datastore/preferences/core/AtomicBoolean;->(Z)V +HSPLandroidx/datastore/preferences/core/AtomicBoolean;->get()Z +HSPLandroidx/datastore/preferences/core/AtomicBoolean;->set(Z)V +HSPLandroidx/datastore/preferences/core/MutablePreferences;->(Ljava/util/Map;Z)V +HSPLandroidx/datastore/preferences/core/MutablePreferences;->(Ljava/util/Map;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/preferences/core/MutablePreferences;->asMap()Ljava/util/Map; +HSPLandroidx/datastore/preferences/core/MutablePreferences;->checkNotFrozen$datastore_preferences_core()V +HSPLandroidx/datastore/preferences/core/MutablePreferences;->equals(Ljava/lang/Object;)Z +HSPLandroidx/datastore/preferences/core/MutablePreferences;->freeze$datastore_preferences_core()V +HSPLandroidx/datastore/preferences/core/MutablePreferences;->get(Landroidx/datastore/preferences/core/Preferences$Key;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/core/MutablePreferences;->hashCode()I +HSPLandroidx/datastore/preferences/core/MutablePreferences;->putAll([Landroidx/datastore/preferences/core/Preferences$Pair;)V +HSPLandroidx/datastore/preferences/core/MutablePreferences;->set(Landroidx/datastore/preferences/core/Preferences$Key;Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/core/MutablePreferences;->setUnchecked$datastore_preferences_core(Landroidx/datastore/preferences/core/Preferences$Key;Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/core/PreferenceDataStore$updateData$2;->(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/preferences/core/PreferenceDataStore$updateData$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +HSPLandroidx/datastore/preferences/core/PreferenceDataStore$updateData$2;->invoke(Landroidx/datastore/preferences/core/Preferences;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/core/PreferenceDataStore$updateData$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/core/PreferenceDataStore$updateData$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/core/PreferenceDataStore;->(Landroidx/datastore/core/DataStore;)V +HSPLandroidx/datastore/preferences/core/PreferenceDataStore;->getData()Lkotlinx/coroutines/flow/Flow; +HSPLandroidx/datastore/preferences/core/PreferenceDataStore;->updateData(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/core/PreferenceDataStoreFactory$$ExternalSyntheticLambda0;->(Lkotlin/jvm/functions/Function0;)V +HSPLandroidx/datastore/preferences/core/PreferenceDataStoreFactory$$ExternalSyntheticLambda0;->invoke()Ljava/lang/Object; +HSPLandroidx/datastore/preferences/core/PreferenceDataStoreFactory;->$r8$lambda$9BrpQzy58wyiliC4ZrxXEeAelC0(Lkotlin/jvm/functions/Function0;)Ljava/io/File; +HSPLandroidx/datastore/preferences/core/PreferenceDataStoreFactory;->()V +HSPLandroidx/datastore/preferences/core/PreferenceDataStoreFactory;->()V +HSPLandroidx/datastore/preferences/core/PreferenceDataStoreFactory;->create$lambda$0(Lkotlin/jvm/functions/Function0;)Ljava/io/File; +HSPLandroidx/datastore/preferences/core/PreferenceDataStoreFactory;->create(Landroidx/datastore/core/Storage;Landroidx/datastore/core/handlers/ReplaceFileCorruptionHandler;Ljava/util/List;Lkotlinx/coroutines/CoroutineScope;)Landroidx/datastore/core/DataStore; +HSPLandroidx/datastore/preferences/core/PreferenceDataStoreFactory;->create(Landroidx/datastore/core/handlers/ReplaceFileCorruptionHandler;Ljava/util/List;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;)Landroidx/datastore/core/DataStore; +HSPLandroidx/datastore/preferences/core/Preferences$Key;->(Ljava/lang/String;)V +HSPLandroidx/datastore/preferences/core/Preferences$Key;->equals(Ljava/lang/Object;)Z +HSPLandroidx/datastore/preferences/core/Preferences$Key;->getName()Ljava/lang/String; +HSPLandroidx/datastore/preferences/core/Preferences$Key;->hashCode()I +HSPLandroidx/datastore/preferences/core/Preferences;->()V +HSPLandroidx/datastore/preferences/core/Preferences;->toMutablePreferences()Landroidx/datastore/preferences/core/MutablePreferences; +HSPLandroidx/datastore/preferences/core/Preferences;->toPreferences()Landroidx/datastore/preferences/core/Preferences; +HSPLandroidx/datastore/preferences/core/PreferencesFactory;->createEmpty()Landroidx/datastore/preferences/core/Preferences; +HSPLandroidx/datastore/preferences/core/PreferencesFactory;->createMutable([Landroidx/datastore/preferences/core/Preferences$Pair;)Landroidx/datastore/preferences/core/MutablePreferences; +HSPLandroidx/datastore/preferences/core/PreferencesFileSerializer$WhenMappings;->()V +HSPLandroidx/datastore/preferences/core/PreferencesFileSerializer;->()V +HSPLandroidx/datastore/preferences/core/PreferencesFileSerializer;->()V +HSPLandroidx/datastore/preferences/core/PreferencesFileSerializer;->addProtoEntryToPreferences(Ljava/lang/String;Landroidx/datastore/preferences/PreferencesProto$Value;Landroidx/datastore/preferences/core/MutablePreferences;)V +HSPLandroidx/datastore/preferences/core/PreferencesFileSerializer;->getDefaultValue()Landroidx/datastore/preferences/core/Preferences; +HSPLandroidx/datastore/preferences/core/PreferencesFileSerializer;->getDefaultValue()Ljava/lang/Object; +HSPLandroidx/datastore/preferences/core/PreferencesFileSerializer;->getValueProto(Ljava/lang/Object;)Landroidx/datastore/preferences/PreferencesProto$Value; +HSPLandroidx/datastore/preferences/core/PreferencesFileSerializer;->readFrom(Ljava/io/InputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/core/PreferencesFileSerializer;->writeTo(Landroidx/datastore/preferences/core/Preferences;Ljava/io/OutputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/core/PreferencesFileSerializer;->writeTo(Ljava/lang/Object;Ljava/io/OutputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/core/PreferencesKeys;->booleanKey(Ljava/lang/String;)Landroidx/datastore/preferences/core/Preferences$Key; +HSPLandroidx/datastore/preferences/core/PreferencesKeys;->doubleKey(Ljava/lang/String;)Landroidx/datastore/preferences/core/Preferences$Key; +HSPLandroidx/datastore/preferences/core/PreferencesKeys;->floatKey(Ljava/lang/String;)Landroidx/datastore/preferences/core/Preferences$Key; +HSPLandroidx/datastore/preferences/core/PreferencesKeys;->intKey(Ljava/lang/String;)Landroidx/datastore/preferences/core/Preferences$Key; +HSPLandroidx/datastore/preferences/core/PreferencesKeys;->longKey(Ljava/lang/String;)Landroidx/datastore/preferences/core/Preferences$Key; +HSPLandroidx/datastore/preferences/core/PreferencesKeys;->stringKey(Ljava/lang/String;)Landroidx/datastore/preferences/core/Preferences$Key; +HSPLandroidx/datastore/preferences/protobuf/AbstractMessageLite$Builder;->()V +HSPLandroidx/datastore/preferences/protobuf/AbstractMessageLite;->()V +HSPLandroidx/datastore/preferences/protobuf/AbstractMessageLite;->writeTo(Ljava/io/OutputStream;)V +HSPLandroidx/datastore/preferences/protobuf/Android;->()V +HSPLandroidx/datastore/preferences/protobuf/Android;->getClassForName(Ljava/lang/String;)Ljava/lang/Class; +HSPLandroidx/datastore/preferences/protobuf/Android;->getMemoryClass()Ljava/lang/Class; +HSPLandroidx/datastore/preferences/protobuf/Android;->isOnAndroidDevice()Z +HSPLandroidx/datastore/preferences/protobuf/ByteOutput;->()V +HSPLandroidx/datastore/preferences/protobuf/ByteString$2;->()V +HSPLandroidx/datastore/preferences/protobuf/ByteString$LeafByteString;->()V +HSPLandroidx/datastore/preferences/protobuf/ByteString$LeafByteString;->(Landroidx/datastore/preferences/protobuf/ByteString$1;)V +HSPLandroidx/datastore/preferences/protobuf/ByteString$LiteralByteString;->([B)V +HSPLandroidx/datastore/preferences/protobuf/ByteString$SystemByteArrayCopier;->()V +HSPLandroidx/datastore/preferences/protobuf/ByteString$SystemByteArrayCopier;->(Landroidx/datastore/preferences/protobuf/ByteString$1;)V +HSPLandroidx/datastore/preferences/protobuf/ByteString;->()V +HSPLandroidx/datastore/preferences/protobuf/ByteString;->()V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$ArrayDecoder;->([BIIZ)V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$ArrayDecoder;->([BIIZLandroidx/datastore/preferences/protobuf/CodedInputStream$1;)V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$ArrayDecoder;->getTotalBytesRead()I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$ArrayDecoder;->pushLimit(I)I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$ArrayDecoder;->recomputeBufferSizeAfterLimit()V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->(Ljava/io/InputStream;I)V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->(Ljava/io/InputStream;ILandroidx/datastore/preferences/protobuf/CodedInputStream$1;)V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->checkLastTagWas(I)V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->isAtEnd()Z +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->popLimit(I)V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->pushLimit(I)I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->read(Ljava/io/InputStream;[BII)I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readBool()Z +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readDouble()D +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readFloat()F +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readInt32()I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readInt64()J +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readRawLittleEndian32()I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readRawLittleEndian64()J +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readRawVarint32()I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readRawVarint64()J +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readString()Ljava/lang/String; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readStringRequireUtf8()Ljava/lang/String; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readTag()I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readUInt32()I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->recomputeBufferSizeAfterLimit()V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->tryRefillBuffer(I)Z +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream;->()V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream;->()V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream;->(Landroidx/datastore/preferences/protobuf/CodedInputStream$1;)V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream;->newInstance(Ljava/io/InputStream;)Landroidx/datastore/preferences/protobuf/CodedInputStream; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream;->newInstance(Ljava/io/InputStream;I)Landroidx/datastore/preferences/protobuf/CodedInputStream; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream;->newInstance([B)Landroidx/datastore/preferences/protobuf/CodedInputStream; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream;->newInstance([BII)Landroidx/datastore/preferences/protobuf/CodedInputStream; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStream;->newInstance([BIIZ)Landroidx/datastore/preferences/protobuf/CodedInputStream; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader$1;->()V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->(Landroidx/datastore/preferences/protobuf/CodedInputStream;)V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->forCodedInput(Landroidx/datastore/preferences/protobuf/CodedInputStream;)Landroidx/datastore/preferences/protobuf/CodedInputStreamReader; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->getFieldNumber()I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->mergeMessageFieldInternal(Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Schema;Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite;)V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readBool()Z +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readDouble()D +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readField(Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Class;Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readFloat()F +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readInt32()I +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readInt64()J +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readMap(Ljava/util/Map;Landroidx/datastore/preferences/protobuf/MapEntryLite$Metadata;Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite;)V +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readMessage(Landroidx/datastore/preferences/protobuf/Schema;Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readMessage(Ljava/lang/Class;Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readString()Ljava/lang/String; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readStringRequireUtf8()Ljava/lang/String; +HSPLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->requireWireType(I)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->(I)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->bufferInt32NoTag(I)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->bufferTag(II)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->bufferUInt32NoTag(I)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->(Ljava/io/OutputStream;I)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->doFlush()V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->flush()V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->flushIfNotAvailable(I)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->write([BII)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeInt32(II)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeLazy([BII)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeMessageNoTag(Landroidx/datastore/preferences/protobuf/MessageLite;)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeString(ILjava/lang/String;)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeStringNoTag(Ljava/lang/String;)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeTag(II)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeUInt32NoTag(I)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->()V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->()V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->(Landroidx/datastore/preferences/protobuf/CodedOutputStream$1;)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->access$100()Z +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeInt32Size(II)I +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeInt32SizeNoTag(I)I +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeLengthDelimitedFieldSize(I)I +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeMessageSizeNoTag(Landroidx/datastore/preferences/protobuf/MessageLite;)I +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computePreferredBufferSize(I)I +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeStringSize(ILjava/lang/String;)I +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeStringSizeNoTag(Ljava/lang/String;)I +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeTagSize(I)I +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeUInt32SizeNoTag(I)I +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeUInt64SizeNoTag(J)I +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->isSerializationDeterministic()Z +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStream;->newInstance(Ljava/io/OutputStream;I)Landroidx/datastore/preferences/protobuf/CodedOutputStream; +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->(Landroidx/datastore/preferences/protobuf/CodedOutputStream;)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->fieldOrder()Landroidx/datastore/preferences/protobuf/Writer$FieldOrder; +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->forCodedOutput(Landroidx/datastore/preferences/protobuf/CodedOutputStream;)Landroidx/datastore/preferences/protobuf/CodedOutputStreamWriter; +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->writeInt32(II)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->writeMap(ILandroidx/datastore/preferences/protobuf/MapEntryLite$Metadata;Ljava/util/Map;)V +HSPLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->writeString(ILjava/lang/String;)V +HSPLandroidx/datastore/preferences/protobuf/ExtensionRegistryFactory;->()V +HSPLandroidx/datastore/preferences/protobuf/ExtensionRegistryFactory;->createEmpty()Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite; +HSPLandroidx/datastore/preferences/protobuf/ExtensionRegistryFactory;->invokeSubclassFactory(Ljava/lang/String;)Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite; +HSPLandroidx/datastore/preferences/protobuf/ExtensionRegistryFactory;->reflectExtensionRegistry()Ljava/lang/Class; +HSPLandroidx/datastore/preferences/protobuf/ExtensionRegistryLite;->()V +HSPLandroidx/datastore/preferences/protobuf/ExtensionRegistryLite;->(Z)V +HSPLandroidx/datastore/preferences/protobuf/ExtensionRegistryLite;->getEmptyRegistry()Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite; +HSPLandroidx/datastore/preferences/protobuf/ExtensionSchema;->()V +HSPLandroidx/datastore/preferences/protobuf/ExtensionSchemaLite;->()V +HSPLandroidx/datastore/preferences/protobuf/ExtensionSchemaLite;->hasExtensions(Landroidx/datastore/preferences/protobuf/MessageLite;)Z +HSPLandroidx/datastore/preferences/protobuf/ExtensionSchemas;->()V +HSPLandroidx/datastore/preferences/protobuf/ExtensionSchemas;->lite()Landroidx/datastore/preferences/protobuf/ExtensionSchema; +HSPLandroidx/datastore/preferences/protobuf/ExtensionSchemas;->loadSchemaForFullRuntime()Landroidx/datastore/preferences/protobuf/ExtensionSchema; +HSPLandroidx/datastore/preferences/protobuf/FieldSet$1;->()V +HSPLandroidx/datastore/preferences/protobuf/FieldSet;->()V +HSPLandroidx/datastore/preferences/protobuf/FieldSet;->(Landroidx/datastore/preferences/protobuf/SmallSortedMap;)V +HSPLandroidx/datastore/preferences/protobuf/FieldSet;->(Z)V +HSPLandroidx/datastore/preferences/protobuf/FieldSet;->computeElementSize(Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;ILjava/lang/Object;)I +HSPLandroidx/datastore/preferences/protobuf/FieldSet;->computeElementSizeNoTag(Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Object;)I +HSPLandroidx/datastore/preferences/protobuf/FieldSet;->getWireFormatForFieldType(Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Z)I +HSPLandroidx/datastore/preferences/protobuf/FieldSet;->makeImmutable()V +HSPLandroidx/datastore/preferences/protobuf/FieldSet;->writeElement(Landroidx/datastore/preferences/protobuf/CodedOutputStream;Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;ILjava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/FieldSet;->writeElementNoTag(Landroidx/datastore/preferences/protobuf/CodedOutputStream;Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/FieldType$1;->()V +HSPLandroidx/datastore/preferences/protobuf/FieldType$Collection;->()V +HSPLandroidx/datastore/preferences/protobuf/FieldType$Collection;->(Ljava/lang/String;IZ)V +HSPLandroidx/datastore/preferences/protobuf/FieldType$Collection;->values()[Landroidx/datastore/preferences/protobuf/FieldType$Collection; +HSPLandroidx/datastore/preferences/protobuf/FieldType;->()V +HSPLandroidx/datastore/preferences/protobuf/FieldType;->(Ljava/lang/String;IILandroidx/datastore/preferences/protobuf/FieldType$Collection;Landroidx/datastore/preferences/protobuf/JavaType;)V +HSPLandroidx/datastore/preferences/protobuf/FieldType;->id()I +HSPLandroidx/datastore/preferences/protobuf/FieldType;->values()[Landroidx/datastore/preferences/protobuf/FieldType; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageInfoFactory;->()V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageInfoFactory;->()V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageInfoFactory;->getInstance()Landroidx/datastore/preferences/protobuf/GeneratedMessageInfoFactory; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageInfoFactory;->isSupported(Ljava/lang/Class;)Z +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageInfoFactory;->messageInfoFor(Ljava/lang/Class;)Landroidx/datastore/preferences/protobuf/MessageInfo; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder;->(Landroidx/datastore/preferences/protobuf/GeneratedMessageLite;)V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder;->build()Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder;->buildPartial()Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder;->copyOnWrite()V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder;->newMutableInstance()Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$MethodToInvoke;->()V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$MethodToInvoke;->(Ljava/lang/String;I)V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$MethodToInvoke;->values()[Landroidx/datastore/preferences/protobuf/GeneratedMessageLite$MethodToInvoke; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->()V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->()V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->buildMessageInfo()Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->checkMessageInitialized(Landroidx/datastore/preferences/protobuf/GeneratedMessageLite;)Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->clearMemoizedHashCode()V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->clearMemoizedSerializedSize()V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->computeSerializedSize(Landroidx/datastore/preferences/protobuf/Schema;)I +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->createBuilder()Landroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->dynamicMethod(Landroidx/datastore/preferences/protobuf/GeneratedMessageLite$MethodToInvoke;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->getDefaultInstance(Ljava/lang/Class;)Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->getMemoizedSerializedSize()I +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->getSerializedSize()I +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->getSerializedSize(Landroidx/datastore/preferences/protobuf/Schema;)I +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->isInitialized()Z +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->isInitialized(Landroidx/datastore/preferences/protobuf/GeneratedMessageLite;Z)Z +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->isMutable()Z +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->makeImmutable()V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->markImmutable()V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->newMessageInfo(Landroidx/datastore/preferences/protobuf/MessageLite;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->newMutableInstance()Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->parseFrom(Landroidx/datastore/preferences/protobuf/GeneratedMessageLite;Ljava/io/InputStream;)Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->parsePartialFrom(Landroidx/datastore/preferences/protobuf/GeneratedMessageLite;Landroidx/datastore/preferences/protobuf/CodedInputStream;Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite;)Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->registerDefaultInstance(Ljava/lang/Class;Landroidx/datastore/preferences/protobuf/GeneratedMessageLite;)V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->setMemoizedSerializedSize(I)V +HSPLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->writeTo(Landroidx/datastore/preferences/protobuf/CodedOutputStream;)V +HSPLandroidx/datastore/preferences/protobuf/Internal;->()V +HSPLandroidx/datastore/preferences/protobuf/Internal;->checkNotNull(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/Internal;->checkNotNull(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/JavaType;->()V +HSPLandroidx/datastore/preferences/protobuf/JavaType;->(Ljava/lang/String;ILjava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/JavaType;->getBoxedType()Ljava/lang/Class; +HSPLandroidx/datastore/preferences/protobuf/JavaType;->values()[Landroidx/datastore/preferences/protobuf/JavaType; +HSPLandroidx/datastore/preferences/protobuf/ListFieldSchemaLite;->()V +HSPLandroidx/datastore/preferences/protobuf/ListFieldSchemas;->()V +HSPLandroidx/datastore/preferences/protobuf/ListFieldSchemas;->lite()Landroidx/datastore/preferences/protobuf/ListFieldSchema; +HSPLandroidx/datastore/preferences/protobuf/ListFieldSchemas;->loadSchemaForFullRuntime()Landroidx/datastore/preferences/protobuf/ListFieldSchema; +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory$1;->()V +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory$2;->()V +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory$CompositeMessageInfoFactory;->([Landroidx/datastore/preferences/protobuf/MessageInfoFactory;)V +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory$CompositeMessageInfoFactory;->messageInfoFor(Ljava/lang/Class;)Landroidx/datastore/preferences/protobuf/MessageInfo; +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory;->()V +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory;->()V +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory;->(Landroidx/datastore/preferences/protobuf/MessageInfoFactory;)V +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory;->allowExtensions(Landroidx/datastore/preferences/protobuf/MessageInfo;)Z +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory;->createSchema(Ljava/lang/Class;)Landroidx/datastore/preferences/protobuf/Schema; +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory;->getDefaultMessageInfoFactory()Landroidx/datastore/preferences/protobuf/MessageInfoFactory; +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory;->getDescriptorMessageInfoFactory()Landroidx/datastore/preferences/protobuf/MessageInfoFactory; +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory;->newSchema(Ljava/lang/Class;Landroidx/datastore/preferences/protobuf/MessageInfo;)Landroidx/datastore/preferences/protobuf/Schema; +HSPLandroidx/datastore/preferences/protobuf/ManifestSchemaFactory;->useLiteRuntime(Ljava/lang/Class;)Z +HSPLandroidx/datastore/preferences/protobuf/MapEntryLite$Metadata;->(Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/MapEntryLite;->(Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/MapEntryLite;->computeMessageSize(ILjava/lang/Object;Ljava/lang/Object;)I +HSPLandroidx/datastore/preferences/protobuf/MapEntryLite;->computeSerializedSize(Landroidx/datastore/preferences/protobuf/MapEntryLite$Metadata;Ljava/lang/Object;Ljava/lang/Object;)I +HSPLandroidx/datastore/preferences/protobuf/MapEntryLite;->getMetadata()Landroidx/datastore/preferences/protobuf/MapEntryLite$Metadata; +HSPLandroidx/datastore/preferences/protobuf/MapEntryLite;->newDefaultInstance(Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Object;)Landroidx/datastore/preferences/protobuf/MapEntryLite; +HSPLandroidx/datastore/preferences/protobuf/MapEntryLite;->writeTo(Landroidx/datastore/preferences/protobuf/CodedOutputStream;Landroidx/datastore/preferences/protobuf/MapEntryLite$Metadata;Ljava/lang/Object;Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/MapFieldLite;->()V +HSPLandroidx/datastore/preferences/protobuf/MapFieldLite;->()V +HSPLandroidx/datastore/preferences/protobuf/MapFieldLite;->emptyMapField()Landroidx/datastore/preferences/protobuf/MapFieldLite; +HSPLandroidx/datastore/preferences/protobuf/MapFieldLite;->ensureMutable()V +HSPLandroidx/datastore/preferences/protobuf/MapFieldLite;->entrySet()Ljava/util/Set; +HSPLandroidx/datastore/preferences/protobuf/MapFieldLite;->isMutable()Z +HSPLandroidx/datastore/preferences/protobuf/MapFieldLite;->makeImmutable()V +HSPLandroidx/datastore/preferences/protobuf/MapFieldLite;->mutableCopy()Landroidx/datastore/preferences/protobuf/MapFieldLite; +HSPLandroidx/datastore/preferences/protobuf/MapFieldLite;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->()V +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->forMapData(Ljava/lang/Object;)Ljava/util/Map; +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->forMapMetadata(Ljava/lang/Object;)Landroidx/datastore/preferences/protobuf/MapEntryLite$Metadata; +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->forMutableMapData(Ljava/lang/Object;)Ljava/util/Map; +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->getSerializedSize(ILjava/lang/Object;Ljava/lang/Object;)I +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->getSerializedSizeLite(ILjava/lang/Object;Ljava/lang/Object;)I +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->isImmutable(Ljava/lang/Object;)Z +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->mergeFrom(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->mergeFromLite(Ljava/lang/Object;Ljava/lang/Object;)Landroidx/datastore/preferences/protobuf/MapFieldLite; +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->newMapField(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->toImmutable(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemas;->()V +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemas;->lite()Landroidx/datastore/preferences/protobuf/MapFieldSchema; +HSPLandroidx/datastore/preferences/protobuf/MapFieldSchemas;->loadSchemaForFullRuntime()Landroidx/datastore/preferences/protobuf/MapFieldSchema; +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->()V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->([I[Ljava/lang/Object;IILandroidx/datastore/preferences/protobuf/MessageLite;Landroidx/datastore/preferences/protobuf/ProtoSyntax;Z[IIILandroidx/datastore/preferences/protobuf/NewInstanceSchema;Landroidx/datastore/preferences/protobuf/ListFieldSchema;Landroidx/datastore/preferences/protobuf/UnknownFieldSchema;Landroidx/datastore/preferences/protobuf/ExtensionSchema;Landroidx/datastore/preferences/protobuf/MapFieldSchema;)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->checkMutable(Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->filterMapUnknownEnumValues(Ljava/lang/Object;ILjava/lang/Object;Landroidx/datastore/preferences/protobuf/UnknownFieldSchema;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->getEnumFieldVerifier(I)Landroidx/datastore/preferences/protobuf/Internal$EnumVerifier; +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->getMapFieldDefaultEntry(I)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->getSerializedSize(Ljava/lang/Object;)I +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->getUnknownFieldsSerializedSize(Landroidx/datastore/preferences/protobuf/UnknownFieldSchema;Ljava/lang/Object;)I +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->isEnforceUtf8(I)Z +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->isMutable(Ljava/lang/Object;)Z +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->isOneofPresent(Ljava/lang/Object;II)Z +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->makeImmutable(Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->mergeFrom(Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Reader;Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite;)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->mergeFromHelper(Landroidx/datastore/preferences/protobuf/UnknownFieldSchema;Landroidx/datastore/preferences/protobuf/ExtensionSchema;Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Reader;Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite;)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->mergeMap(Ljava/lang/Object;ILjava/lang/Object;Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite;Landroidx/datastore/preferences/protobuf/Reader;)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->newInstance()Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->newSchema(Ljava/lang/Class;Landroidx/datastore/preferences/protobuf/MessageInfo;Landroidx/datastore/preferences/protobuf/NewInstanceSchema;Landroidx/datastore/preferences/protobuf/ListFieldSchema;Landroidx/datastore/preferences/protobuf/UnknownFieldSchema;Landroidx/datastore/preferences/protobuf/ExtensionSchema;Landroidx/datastore/preferences/protobuf/MapFieldSchema;)Landroidx/datastore/preferences/protobuf/MessageSchema; +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->newSchemaForRawMessageInfo(Landroidx/datastore/preferences/protobuf/RawMessageInfo;Landroidx/datastore/preferences/protobuf/NewInstanceSchema;Landroidx/datastore/preferences/protobuf/ListFieldSchema;Landroidx/datastore/preferences/protobuf/UnknownFieldSchema;Landroidx/datastore/preferences/protobuf/ExtensionSchema;Landroidx/datastore/preferences/protobuf/MapFieldSchema;)Landroidx/datastore/preferences/protobuf/MessageSchema; +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->numberAt(I)I +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->offset(I)J +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->oneofIntAt(Ljava/lang/Object;J)I +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->positionForFieldNumber(I)I +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->presenceMaskAndOffsetAt(I)I +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->readString(Ljava/lang/Object;ILandroidx/datastore/preferences/protobuf/Reader;)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->reflectField(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field; +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->setOneofPresent(Ljava/lang/Object;II)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->slowPositionForFieldNumber(II)I +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->type(I)I +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->typeAndOffsetAt(I)I +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->writeFieldsInAscendingOrder(Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Writer;)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->writeMapHelper(Landroidx/datastore/preferences/protobuf/Writer;ILjava/lang/Object;I)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->writeString(ILjava/lang/Object;Landroidx/datastore/preferences/protobuf/Writer;)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->writeTo(Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Writer;)V +HSPLandroidx/datastore/preferences/protobuf/MessageSchema;->writeUnknownInMessageTo(Landroidx/datastore/preferences/protobuf/UnknownFieldSchema;Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Writer;)V +HSPLandroidx/datastore/preferences/protobuf/NewInstanceSchemaLite;->()V +HSPLandroidx/datastore/preferences/protobuf/NewInstanceSchemaLite;->newInstance(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/NewInstanceSchemas;->()V +HSPLandroidx/datastore/preferences/protobuf/NewInstanceSchemas;->lite()Landroidx/datastore/preferences/protobuf/NewInstanceSchema; +HSPLandroidx/datastore/preferences/protobuf/NewInstanceSchemas;->loadSchemaForFullRuntime()Landroidx/datastore/preferences/protobuf/NewInstanceSchema; +HSPLandroidx/datastore/preferences/protobuf/ProtoSyntax;->()V +HSPLandroidx/datastore/preferences/protobuf/ProtoSyntax;->(Ljava/lang/String;I)V +HSPLandroidx/datastore/preferences/protobuf/ProtoSyntax;->values()[Landroidx/datastore/preferences/protobuf/ProtoSyntax; +HSPLandroidx/datastore/preferences/protobuf/Protobuf;->()V +HSPLandroidx/datastore/preferences/protobuf/Protobuf;->()V +HSPLandroidx/datastore/preferences/protobuf/Protobuf;->getInstance()Landroidx/datastore/preferences/protobuf/Protobuf; +HSPLandroidx/datastore/preferences/protobuf/Protobuf;->registerSchema(Ljava/lang/Class;Landroidx/datastore/preferences/protobuf/Schema;)Landroidx/datastore/preferences/protobuf/Schema; +HSPLandroidx/datastore/preferences/protobuf/Protobuf;->schemaFor(Ljava/lang/Class;)Landroidx/datastore/preferences/protobuf/Schema; +HSPLandroidx/datastore/preferences/protobuf/Protobuf;->schemaFor(Ljava/lang/Object;)Landroidx/datastore/preferences/protobuf/Schema; +HSPLandroidx/datastore/preferences/protobuf/RawMessageInfo;->(Landroidx/datastore/preferences/protobuf/MessageLite;Ljava/lang/String;[Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/RawMessageInfo;->getDefaultInstance()Landroidx/datastore/preferences/protobuf/MessageLite; +HSPLandroidx/datastore/preferences/protobuf/RawMessageInfo;->getObjects()[Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/RawMessageInfo;->getStringInfo()Ljava/lang/String; +HSPLandroidx/datastore/preferences/protobuf/RawMessageInfo;->getSyntax()Landroidx/datastore/preferences/protobuf/ProtoSyntax; +HSPLandroidx/datastore/preferences/protobuf/RawMessageInfo;->isMessageSetWireFormat()Z +HSPLandroidx/datastore/preferences/protobuf/SchemaUtil;->()V +HSPLandroidx/datastore/preferences/protobuf/SchemaUtil;->getGeneratedMessageClass()Ljava/lang/Class; +HSPLandroidx/datastore/preferences/protobuf/SchemaUtil;->getUnknownFieldSetSchema()Landroidx/datastore/preferences/protobuf/UnknownFieldSchema; +HSPLandroidx/datastore/preferences/protobuf/SchemaUtil;->getUnknownFieldSetSchemaClass()Ljava/lang/Class; +HSPLandroidx/datastore/preferences/protobuf/SchemaUtil;->requireGeneratedMessage(Ljava/lang/Class;)V +HSPLandroidx/datastore/preferences/protobuf/SchemaUtil;->unknownFieldSetLiteSchema()Landroidx/datastore/preferences/protobuf/UnknownFieldSchema; +HSPLandroidx/datastore/preferences/protobuf/SmallSortedMap$1;->()V +HSPLandroidx/datastore/preferences/protobuf/SmallSortedMap$1;->makeImmutable()V +HSPLandroidx/datastore/preferences/protobuf/SmallSortedMap;->()V +HSPLandroidx/datastore/preferences/protobuf/SmallSortedMap;->(Landroidx/datastore/preferences/protobuf/SmallSortedMap$1;)V +HSPLandroidx/datastore/preferences/protobuf/SmallSortedMap;->getNumArrayEntries()I +HSPLandroidx/datastore/preferences/protobuf/SmallSortedMap;->getOverflowEntries()Ljava/lang/Iterable; +HSPLandroidx/datastore/preferences/protobuf/SmallSortedMap;->isImmutable()Z +HSPLandroidx/datastore/preferences/protobuf/SmallSortedMap;->makeImmutable()V +HSPLandroidx/datastore/preferences/protobuf/SmallSortedMap;->newFieldMap()Landroidx/datastore/preferences/protobuf/SmallSortedMap; +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSchema;->()V +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSchema;->()V +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLite;->()V +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLite;->(I[I[Ljava/lang/Object;Z)V +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLite;->getDefaultInstance()Landroidx/datastore/preferences/protobuf/UnknownFieldSetLite; +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLite;->getSerializedSize()I +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLite;->makeImmutable()V +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLite;->writeTo(Landroidx/datastore/preferences/protobuf/Writer;)V +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->()V +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->getFromMessage(Ljava/lang/Object;)Landroidx/datastore/preferences/protobuf/UnknownFieldSetLite; +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->getFromMessage(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->getSerializedSize(Landroidx/datastore/preferences/protobuf/UnknownFieldSetLite;)I +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->getSerializedSize(Ljava/lang/Object;)I +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->makeImmutable(Ljava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->writeTo(Landroidx/datastore/preferences/protobuf/UnknownFieldSetLite;Landroidx/datastore/preferences/protobuf/Writer;)V +HSPLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->writeTo(Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Writer;)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$1;->()V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$1;->run()Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$1;->run()Lsun/misc/Unsafe; +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$Android64MemoryAccessor;->(Lsun/misc/Unsafe;)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$Android64MemoryAccessor;->putByte(Ljava/lang/Object;JB)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$Android64MemoryAccessor;->supportsUnsafeByteBufferOperations()Z +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor;->(Lsun/misc/Unsafe;)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor;->arrayBaseOffset(Ljava/lang/Class;)I +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor;->arrayIndexScale(Ljava/lang/Class;)I +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor;->getInt(Ljava/lang/Object;J)I +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor;->getObject(Ljava/lang/Object;J)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor;->objectFieldOffset(Ljava/lang/reflect/Field;)J +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor;->putInt(Ljava/lang/Object;JI)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor;->putObject(Ljava/lang/Object;JLjava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor;->supportsUnsafeArrayOperations()Z +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->()V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->access$500(Ljava/lang/Object;JB)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->arrayBaseOffset(Ljava/lang/Class;)I +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->arrayIndexScale(Ljava/lang/Class;)I +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->bufferAddressField()Ljava/lang/reflect/Field; +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->determineAndroidSupportByAddressSize(Ljava/lang/Class;)Z +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->field(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field; +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->fieldOffset(Ljava/lang/reflect/Field;)J +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->getInt(Ljava/lang/Object;J)I +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->getMemoryAccessor()Landroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor; +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->getObject(Ljava/lang/Object;J)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->getUnsafe()Lsun/misc/Unsafe; +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->hasUnsafeArrayOperations()Z +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->hasUnsafeByteBufferOperations()Z +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->putByte([BJB)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->putByteLittleEndian(Ljava/lang/Object;JB)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->putInt(Ljava/lang/Object;JI)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->putObject(Ljava/lang/Object;JLjava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->supportsUnsafeArrayOperations()Z +HSPLandroidx/datastore/preferences/protobuf/UnsafeUtil;->supportsUnsafeByteBufferOperations()Z +HSPLandroidx/datastore/preferences/protobuf/Utf8$DecodeUtil;->access$400(B)Z +HSPLandroidx/datastore/preferences/protobuf/Utf8$DecodeUtil;->access$500(B[CI)V +HSPLandroidx/datastore/preferences/protobuf/Utf8$DecodeUtil;->handleOneByte(B[CI)V +HSPLandroidx/datastore/preferences/protobuf/Utf8$DecodeUtil;->isOneByte(B)Z +HSPLandroidx/datastore/preferences/protobuf/Utf8$Processor;->()V +HSPLandroidx/datastore/preferences/protobuf/Utf8$SafeProcessor;->()V +HSPLandroidx/datastore/preferences/protobuf/Utf8$SafeProcessor;->decodeUtf8([BII)Ljava/lang/String; +HSPLandroidx/datastore/preferences/protobuf/Utf8$SafeProcessor;->encodeUtf8(Ljava/lang/String;[BII)I +HSPLandroidx/datastore/preferences/protobuf/Utf8$UnsafeProcessor;->isAvailable()Z +HSPLandroidx/datastore/preferences/protobuf/Utf8;->()V +HSPLandroidx/datastore/preferences/protobuf/Utf8;->decodeUtf8([BII)Ljava/lang/String; +HSPLandroidx/datastore/preferences/protobuf/Utf8;->encode(Ljava/lang/String;[BII)I +HSPLandroidx/datastore/preferences/protobuf/Utf8;->encodedLength(Ljava/lang/String;)I +HSPLandroidx/datastore/preferences/protobuf/WireFormat$FieldType$1;->(Ljava/lang/String;ILandroidx/datastore/preferences/protobuf/WireFormat$JavaType;I)V +HSPLandroidx/datastore/preferences/protobuf/WireFormat$FieldType$2;->(Ljava/lang/String;ILandroidx/datastore/preferences/protobuf/WireFormat$JavaType;I)V +HSPLandroidx/datastore/preferences/protobuf/WireFormat$FieldType$3;->(Ljava/lang/String;ILandroidx/datastore/preferences/protobuf/WireFormat$JavaType;I)V +HSPLandroidx/datastore/preferences/protobuf/WireFormat$FieldType$4;->(Ljava/lang/String;ILandroidx/datastore/preferences/protobuf/WireFormat$JavaType;I)V +HSPLandroidx/datastore/preferences/protobuf/WireFormat$FieldType;->()V +HSPLandroidx/datastore/preferences/protobuf/WireFormat$FieldType;->(Ljava/lang/String;ILandroidx/datastore/preferences/protobuf/WireFormat$JavaType;I)V +HSPLandroidx/datastore/preferences/protobuf/WireFormat$FieldType;->(Ljava/lang/String;ILandroidx/datastore/preferences/protobuf/WireFormat$JavaType;ILandroidx/datastore/preferences/protobuf/WireFormat$1;)V +HSPLandroidx/datastore/preferences/protobuf/WireFormat$FieldType;->getWireType()I +HSPLandroidx/datastore/preferences/protobuf/WireFormat$FieldType;->values()[Landroidx/datastore/preferences/protobuf/WireFormat$FieldType; +HSPLandroidx/datastore/preferences/protobuf/WireFormat$JavaType;->()V +HSPLandroidx/datastore/preferences/protobuf/WireFormat$JavaType;->(Ljava/lang/String;ILjava/lang/Object;)V +HSPLandroidx/datastore/preferences/protobuf/WireFormat$JavaType;->values()[Landroidx/datastore/preferences/protobuf/WireFormat$JavaType; +HSPLandroidx/datastore/preferences/protobuf/WireFormat;->()V +HSPLandroidx/datastore/preferences/protobuf/WireFormat;->getTagFieldNumber(I)I +HSPLandroidx/datastore/preferences/protobuf/WireFormat;->getTagWireType(I)I +HSPLandroidx/datastore/preferences/protobuf/WireFormat;->makeTag(II)I +HSPLandroidx/datastore/preferences/protobuf/Writer$FieldOrder;->()V +HSPLandroidx/datastore/preferences/protobuf/Writer$FieldOrder;->(Ljava/lang/String;I)V +Landroidx/datastore/core/okio/AtomicBoolean; +Landroidx/datastore/core/okio/OkioReadScope$readData$1; +Landroidx/datastore/core/okio/OkioReadScope; +Landroidx/datastore/core/okio/OkioSerializer; +Landroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda0; +Landroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda1; +Landroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda2; +Landroidx/datastore/core/okio/OkioStorage$Companion; +Landroidx/datastore/core/okio/OkioStorage; +Landroidx/datastore/core/okio/OkioStorageConnection$readScope$1; +Landroidx/datastore/core/okio/OkioStorageConnection$writeScope$1; +Landroidx/datastore/core/okio/OkioStorageConnection; +Landroidx/datastore/core/okio/OkioStorageKt; +Landroidx/datastore/core/okio/OkioWriteScope$writeData$1; +Landroidx/datastore/core/okio/OkioWriteScope; +Landroidx/datastore/core/okio/Synchronizer; +Landroidx/datastore/preferences/PreferencesMapCompat$Companion; +Landroidx/datastore/preferences/PreferencesMapCompat; +Landroidx/datastore/preferences/core/Actual_jvmAndroidKt; +Landroidx/datastore/preferences/core/AtomicBoolean; +Landroidx/datastore/preferences/core/MutablePreferences; +Landroidx/datastore/preferences/core/PreferenceDataStore$updateData$2; +Landroidx/datastore/preferences/core/PreferenceDataStore; +Landroidx/datastore/preferences/core/PreferenceDataStoreFactory$$ExternalSyntheticLambda0; +Landroidx/datastore/preferences/core/PreferenceDataStoreFactory; +Landroidx/datastore/preferences/core/Preferences$Key; +Landroidx/datastore/preferences/core/Preferences$Pair; +Landroidx/datastore/preferences/core/Preferences; +Landroidx/datastore/preferences/core/PreferencesFactory; +Landroidx/datastore/preferences/core/PreferencesFileSerializer$WhenMappings; +Landroidx/datastore/preferences/core/PreferencesFileSerializer; +Landroidx/datastore/preferences/core/PreferencesKeys; +Landroidx/datastore/preferences/protobuf/AbstractMessageLite$Builder; +Landroidx/datastore/preferences/protobuf/AbstractMessageLite; +Landroidx/datastore/preferences/protobuf/Android; +Landroidx/datastore/preferences/protobuf/ByteOutput; +Landroidx/datastore/preferences/protobuf/ByteString$2; +Landroidx/datastore/preferences/protobuf/ByteString$ByteArrayCopier; +Landroidx/datastore/preferences/protobuf/ByteString$LeafByteString; +Landroidx/datastore/preferences/protobuf/ByteString$LiteralByteString; +Landroidx/datastore/preferences/protobuf/ByteString$SystemByteArrayCopier; +Landroidx/datastore/preferences/protobuf/ByteString; +Landroidx/datastore/preferences/protobuf/CodedInputStream$ArrayDecoder; +Landroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder$RefillCallback; +Landroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder; +Landroidx/datastore/preferences/protobuf/CodedInputStream; +Landroidx/datastore/preferences/protobuf/CodedInputStreamReader$1; +Landroidx/datastore/preferences/protobuf/CodedInputStreamReader; +Landroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder; +Landroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder; +Landroidx/datastore/preferences/protobuf/CodedOutputStream; +Landroidx/datastore/preferences/protobuf/CodedOutputStreamWriter; +Landroidx/datastore/preferences/protobuf/ExtensionRegistryFactory; +Landroidx/datastore/preferences/protobuf/ExtensionRegistryLite; +Landroidx/datastore/preferences/protobuf/ExtensionSchema; +Landroidx/datastore/preferences/protobuf/ExtensionSchemaLite; +Landroidx/datastore/preferences/protobuf/ExtensionSchemas; +Landroidx/datastore/preferences/protobuf/FieldSet$1; +Landroidx/datastore/preferences/protobuf/FieldSet; +Landroidx/datastore/preferences/protobuf/FieldType$1; +Landroidx/datastore/preferences/protobuf/FieldType$Collection; +Landroidx/datastore/preferences/protobuf/FieldType; +Landroidx/datastore/preferences/protobuf/GeneratedMessageInfoFactory; +Landroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder; +Landroidx/datastore/preferences/protobuf/GeneratedMessageLite$ExtendableMessage; +Landroidx/datastore/preferences/protobuf/GeneratedMessageLite$ExtendableMessageOrBuilder; +Landroidx/datastore/preferences/protobuf/GeneratedMessageLite$MethodToInvoke; +Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +Landroidx/datastore/preferences/protobuf/Internal$EnumVerifier; +Landroidx/datastore/preferences/protobuf/Internal; +Landroidx/datastore/preferences/protobuf/InvalidProtocolBufferException$InvalidWireTypeException; +Landroidx/datastore/preferences/protobuf/InvalidProtocolBufferException; +Landroidx/datastore/preferences/protobuf/JavaType; +Landroidx/datastore/preferences/protobuf/LazyField; +Landroidx/datastore/preferences/protobuf/LazyFieldLite; +Landroidx/datastore/preferences/protobuf/ListFieldSchema; +Landroidx/datastore/preferences/protobuf/ListFieldSchemaLite; +Landroidx/datastore/preferences/protobuf/ListFieldSchemas; +Landroidx/datastore/preferences/protobuf/ManifestSchemaFactory$1; +Landroidx/datastore/preferences/protobuf/ManifestSchemaFactory$2; +Landroidx/datastore/preferences/protobuf/ManifestSchemaFactory$CompositeMessageInfoFactory; +Landroidx/datastore/preferences/protobuf/ManifestSchemaFactory; +Landroidx/datastore/preferences/protobuf/MapEntryLite$Metadata; +Landroidx/datastore/preferences/protobuf/MapEntryLite; +Landroidx/datastore/preferences/protobuf/MapFieldLite; +Landroidx/datastore/preferences/protobuf/MapFieldSchema; +Landroidx/datastore/preferences/protobuf/MapFieldSchemaLite; +Landroidx/datastore/preferences/protobuf/MapFieldSchemas; +Landroidx/datastore/preferences/protobuf/MessageInfo; +Landroidx/datastore/preferences/protobuf/MessageInfoFactory; +Landroidx/datastore/preferences/protobuf/MessageLite$Builder; +Landroidx/datastore/preferences/protobuf/MessageLite; +Landroidx/datastore/preferences/protobuf/MessageLiteOrBuilder; +Landroidx/datastore/preferences/protobuf/MessageSchema; +Landroidx/datastore/preferences/protobuf/NewInstanceSchema; +Landroidx/datastore/preferences/protobuf/NewInstanceSchemaLite; +Landroidx/datastore/preferences/protobuf/NewInstanceSchemas; +Landroidx/datastore/preferences/protobuf/ProtoSyntax; +Landroidx/datastore/preferences/protobuf/Protobuf; +Landroidx/datastore/preferences/protobuf/RawMessageInfo; +Landroidx/datastore/preferences/protobuf/Reader; +Landroidx/datastore/preferences/protobuf/Schema; +Landroidx/datastore/preferences/protobuf/SchemaFactory; +Landroidx/datastore/preferences/protobuf/SchemaUtil; +Landroidx/datastore/preferences/protobuf/SmallSortedMap$1; +Landroidx/datastore/preferences/protobuf/SmallSortedMap; +Landroidx/datastore/preferences/protobuf/UninitializedMessageException; +Landroidx/datastore/preferences/protobuf/UnknownFieldSchema; +Landroidx/datastore/preferences/protobuf/UnknownFieldSetLite; +Landroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema; +Landroidx/datastore/preferences/protobuf/UnsafeUtil$1; +Landroidx/datastore/preferences/protobuf/UnsafeUtil$Android64MemoryAccessor; +Landroidx/datastore/preferences/protobuf/UnsafeUtil$MemoryAccessor; +Landroidx/datastore/preferences/protobuf/UnsafeUtil; +Landroidx/datastore/preferences/protobuf/Utf8$DecodeUtil; +Landroidx/datastore/preferences/protobuf/Utf8$Processor; +Landroidx/datastore/preferences/protobuf/Utf8$SafeProcessor; +Landroidx/datastore/preferences/protobuf/Utf8$UnpairedSurrogateException; +Landroidx/datastore/preferences/protobuf/Utf8$UnsafeProcessor; +Landroidx/datastore/preferences/protobuf/Utf8; +Landroidx/datastore/preferences/protobuf/WireFormat$FieldType$1; +Landroidx/datastore/preferences/protobuf/WireFormat$FieldType$2; +Landroidx/datastore/preferences/protobuf/WireFormat$FieldType$3; +Landroidx/datastore/preferences/protobuf/WireFormat$FieldType$4; +Landroidx/datastore/preferences/protobuf/WireFormat$FieldType; +Landroidx/datastore/preferences/protobuf/WireFormat$JavaType; +Landroidx/datastore/preferences/protobuf/WireFormat; +Landroidx/datastore/preferences/protobuf/Writer$FieldOrder; +Landroidx/datastore/preferences/protobuf/Writer; +PLandroidx/datastore/core/okio/OkioReadScope;->getFileSystem()Lokio/FileSystem; +PLandroidx/datastore/core/okio/OkioReadScope;->getPath()Lokio/Path; +PLandroidx/datastore/core/okio/OkioReadScope;->getSerializer()Landroidx/datastore/core/okio/OkioSerializer; +PLandroidx/datastore/core/okio/OkioStorageConnection$writeScope$1;->(Landroidx/datastore/core/okio/OkioStorageConnection;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/okio/OkioStorageConnection;->writeScope(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/okio/OkioWriteScope$writeData$1;->(Landroidx/datastore/core/okio/OkioWriteScope;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/okio/OkioWriteScope;->(Lokio/FileSystem;Lokio/Path;Landroidx/datastore/core/okio/OkioSerializer;)V +PLandroidx/datastore/core/okio/OkioWriteScope;->writeData(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/preferences/core/AtomicBoolean;->set(Z)V +PLandroidx/datastore/preferences/core/MutablePreferences;->freeze$datastore_preferences_core()V +PLandroidx/datastore/preferences/core/PreferenceDataStore$updateData$2;->(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/preferences/core/PreferenceDataStore$updateData$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; +PLandroidx/datastore/preferences/core/PreferenceDataStore$updateData$2;->invoke(Landroidx/datastore/preferences/core/Preferences;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/preferences/core/PreferenceDataStore$updateData$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/preferences/core/PreferenceDataStore$updateData$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/preferences/core/PreferenceDataStore;->updateData(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/preferences/core/Preferences$Key;->getName()Ljava/lang/String; +PLandroidx/datastore/preferences/core/Preferences;->toMutablePreferences()Landroidx/datastore/preferences/core/MutablePreferences; +PLandroidx/datastore/preferences/core/PreferencesFileSerializer;->getValueProto(Ljava/lang/Object;)Landroidx/datastore/preferences/PreferencesProto$Value; +PLandroidx/datastore/preferences/core/PreferencesFileSerializer;->writeTo(Landroidx/datastore/preferences/core/Preferences;Ljava/io/OutputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/preferences/core/PreferencesFileSerializer;->writeTo(Ljava/lang/Object;Ljava/io/OutputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/preferences/protobuf/AbstractMessageLite$Builder;->()V +PLandroidx/datastore/preferences/protobuf/AbstractMessageLite;->writeTo(Ljava/io/OutputStream;)V +PLandroidx/datastore/preferences/protobuf/ByteOutput;->()V +PLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readDouble()D +PLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readFloat()F +PLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readInt32()I +PLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readInt64()J +PLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readRawLittleEndian32()I +PLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readRawLittleEndian64()J +PLandroidx/datastore/preferences/protobuf/CodedInputStream$StreamDecoder;->readRawVarint64()J +PLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readDouble()D +PLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readFloat()F +PLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readInt32()I +PLandroidx/datastore/preferences/protobuf/CodedInputStreamReader;->readInt64()J +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->(I)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->buffer(B)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->bufferFixed32NoTag(I)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->bufferFixed64NoTag(J)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->bufferInt32NoTag(I)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->bufferTag(II)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->bufferUInt32NoTag(I)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$AbstractBufferedEncoder;->bufferUInt64NoTag(J)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->(Ljava/io/OutputStream;I)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->doFlush()V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->flush()V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->flushIfNotAvailable(I)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeBool(IZ)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeFixed32(II)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeFixed64(IJ)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeInt32(II)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeMessageNoTag(Landroidx/datastore/preferences/protobuf/MessageLite;)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeString(ILjava/lang/String;)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeStringNoTag(Ljava/lang/String;)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeTag(II)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeUInt32NoTag(I)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream$OutputStreamEncoder;->writeUInt64(IJ)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->()V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->()V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->(Landroidx/datastore/preferences/protobuf/CodedOutputStream$1;)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->access$100()Z +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeBoolSize(IZ)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeBoolSizeNoTag(Z)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeDoubleSize(ID)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeDoubleSizeNoTag(D)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeFloatSize(IF)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeFloatSizeNoTag(F)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeInt32Size(II)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeInt32SizeNoTag(I)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeInt64Size(IJ)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeInt64SizeNoTag(J)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeLengthDelimitedFieldSize(I)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeMessageSizeNoTag(Landroidx/datastore/preferences/protobuf/MessageLite;)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computePreferredBufferSize(I)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeStringSize(ILjava/lang/String;)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeStringSizeNoTag(Ljava/lang/String;)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeTagSize(I)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeUInt32SizeNoTag(I)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->computeUInt64SizeNoTag(J)I +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->isSerializationDeterministic()Z +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->newInstance(Ljava/io/OutputStream;I)Landroidx/datastore/preferences/protobuf/CodedOutputStream; +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->writeDouble(ID)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->writeFloat(IF)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStream;->writeInt64(IJ)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->(Landroidx/datastore/preferences/protobuf/CodedOutputStream;)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->fieldOrder()Landroidx/datastore/preferences/protobuf/Writer$FieldOrder; +PLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->forCodedOutput(Landroidx/datastore/preferences/protobuf/CodedOutputStream;)Landroidx/datastore/preferences/protobuf/CodedOutputStreamWriter; +PLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->writeBool(IZ)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->writeDouble(ID)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->writeFloat(IF)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->writeInt32(II)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->writeInt64(IJ)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->writeMap(ILandroidx/datastore/preferences/protobuf/MapEntryLite$Metadata;Ljava/util/Map;)V +PLandroidx/datastore/preferences/protobuf/CodedOutputStreamWriter;->writeString(ILjava/lang/String;)V +PLandroidx/datastore/preferences/protobuf/FieldSet$1;->()V +PLandroidx/datastore/preferences/protobuf/FieldSet;->()V +PLandroidx/datastore/preferences/protobuf/FieldSet;->(Landroidx/datastore/preferences/protobuf/SmallSortedMap;)V +PLandroidx/datastore/preferences/protobuf/FieldSet;->(Z)V +PLandroidx/datastore/preferences/protobuf/FieldSet;->computeElementSize(Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;ILjava/lang/Object;)I +PLandroidx/datastore/preferences/protobuf/FieldSet;->computeElementSizeNoTag(Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Object;)I +PLandroidx/datastore/preferences/protobuf/FieldSet;->getWireFormatForFieldType(Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Z)I +PLandroidx/datastore/preferences/protobuf/FieldSet;->makeImmutable()V +PLandroidx/datastore/preferences/protobuf/FieldSet;->writeElement(Landroidx/datastore/preferences/protobuf/CodedOutputStream;Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;ILjava/lang/Object;)V +PLandroidx/datastore/preferences/protobuf/FieldSet;->writeElementNoTag(Landroidx/datastore/preferences/protobuf/CodedOutputStream;Landroidx/datastore/preferences/protobuf/WireFormat$FieldType;Ljava/lang/Object;)V +PLandroidx/datastore/preferences/protobuf/FieldType$1;->()V +PLandroidx/datastore/preferences/protobuf/FieldType$Collection;->()V +PLandroidx/datastore/preferences/protobuf/FieldType$Collection;->(Ljava/lang/String;IZ)V +PLandroidx/datastore/preferences/protobuf/FieldType$Collection;->values()[Landroidx/datastore/preferences/protobuf/FieldType$Collection; +PLandroidx/datastore/preferences/protobuf/FieldType;->()V +PLandroidx/datastore/preferences/protobuf/FieldType;->(Ljava/lang/String;IILandroidx/datastore/preferences/protobuf/FieldType$Collection;Landroidx/datastore/preferences/protobuf/JavaType;)V +PLandroidx/datastore/preferences/protobuf/FieldType;->id()I +PLandroidx/datastore/preferences/protobuf/FieldType;->values()[Landroidx/datastore/preferences/protobuf/FieldType; +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder;->(Landroidx/datastore/preferences/protobuf/GeneratedMessageLite;)V +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder;->build()Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder;->buildPartial()Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder;->copyOnWrite()V +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder;->newMutableInstance()Landroidx/datastore/preferences/protobuf/GeneratedMessageLite; +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->computeSerializedSize(Landroidx/datastore/preferences/protobuf/Schema;)I +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->createBuilder()Landroidx/datastore/preferences/protobuf/GeneratedMessageLite$Builder; +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->getMemoizedSerializedSize()I +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->getSerializedSize()I +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->getSerializedSize(Landroidx/datastore/preferences/protobuf/Schema;)I +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->makeImmutable()V +PLandroidx/datastore/preferences/protobuf/GeneratedMessageLite;->writeTo(Landroidx/datastore/preferences/protobuf/CodedOutputStream;)V +PLandroidx/datastore/preferences/protobuf/JavaType;->()V +PLandroidx/datastore/preferences/protobuf/JavaType;->(Ljava/lang/String;ILjava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;)V +PLandroidx/datastore/preferences/protobuf/JavaType;->getBoxedType()Ljava/lang/Class; +PLandroidx/datastore/preferences/protobuf/JavaType;->values()[Landroidx/datastore/preferences/protobuf/JavaType; +PLandroidx/datastore/preferences/protobuf/MapEntryLite;->computeMessageSize(ILjava/lang/Object;Ljava/lang/Object;)I +PLandroidx/datastore/preferences/protobuf/MapEntryLite;->computeSerializedSize(Landroidx/datastore/preferences/protobuf/MapEntryLite$Metadata;Ljava/lang/Object;Ljava/lang/Object;)I +PLandroidx/datastore/preferences/protobuf/MapEntryLite;->writeTo(Landroidx/datastore/preferences/protobuf/CodedOutputStream;Landroidx/datastore/preferences/protobuf/MapEntryLite$Metadata;Ljava/lang/Object;Ljava/lang/Object;)V +PLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->forMapData(Ljava/lang/Object;)Ljava/util/Map; +PLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->getSerializedSize(ILjava/lang/Object;Ljava/lang/Object;)I +PLandroidx/datastore/preferences/protobuf/MapFieldSchemaLite;->getSerializedSizeLite(ILjava/lang/Object;Ljava/lang/Object;)I +PLandroidx/datastore/preferences/protobuf/MessageSchema;->getSerializedSize(Ljava/lang/Object;)I +PLandroidx/datastore/preferences/protobuf/MessageSchema;->getUnknownFieldsSerializedSize(Landroidx/datastore/preferences/protobuf/UnknownFieldSchema;Ljava/lang/Object;)I +PLandroidx/datastore/preferences/protobuf/MessageSchema;->oneofBooleanAt(Ljava/lang/Object;J)Z +PLandroidx/datastore/preferences/protobuf/MessageSchema;->oneofDoubleAt(Ljava/lang/Object;J)D +PLandroidx/datastore/preferences/protobuf/MessageSchema;->oneofFloatAt(Ljava/lang/Object;J)F +PLandroidx/datastore/preferences/protobuf/MessageSchema;->oneofIntAt(Ljava/lang/Object;J)I +PLandroidx/datastore/preferences/protobuf/MessageSchema;->oneofLongAt(Ljava/lang/Object;J)J +PLandroidx/datastore/preferences/protobuf/MessageSchema;->writeFieldsInAscendingOrder(Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Writer;)V +PLandroidx/datastore/preferences/protobuf/MessageSchema;->writeMapHelper(Landroidx/datastore/preferences/protobuf/Writer;ILjava/lang/Object;I)V +PLandroidx/datastore/preferences/protobuf/MessageSchema;->writeString(ILjava/lang/Object;Landroidx/datastore/preferences/protobuf/Writer;)V +PLandroidx/datastore/preferences/protobuf/MessageSchema;->writeTo(Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Writer;)V +PLandroidx/datastore/preferences/protobuf/MessageSchema;->writeUnknownInMessageTo(Landroidx/datastore/preferences/protobuf/UnknownFieldSchema;Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Writer;)V +PLandroidx/datastore/preferences/protobuf/SmallSortedMap$1;->()V +PLandroidx/datastore/preferences/protobuf/SmallSortedMap$1;->makeImmutable()V +PLandroidx/datastore/preferences/protobuf/SmallSortedMap;->()V +PLandroidx/datastore/preferences/protobuf/SmallSortedMap;->(Landroidx/datastore/preferences/protobuf/SmallSortedMap$1;)V +PLandroidx/datastore/preferences/protobuf/SmallSortedMap;->getNumArrayEntries()I +PLandroidx/datastore/preferences/protobuf/SmallSortedMap;->getOverflowEntries()Ljava/lang/Iterable; +PLandroidx/datastore/preferences/protobuf/SmallSortedMap;->isImmutable()Z +PLandroidx/datastore/preferences/protobuf/SmallSortedMap;->makeImmutable()V +PLandroidx/datastore/preferences/protobuf/SmallSortedMap;->newFieldMap()Landroidx/datastore/preferences/protobuf/SmallSortedMap; +PLandroidx/datastore/preferences/protobuf/UnknownFieldSetLite;->getSerializedSize()I +PLandroidx/datastore/preferences/protobuf/UnknownFieldSetLite;->writeTo(Landroidx/datastore/preferences/protobuf/Writer;)V +PLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->getFromMessage(Ljava/lang/Object;)Ljava/lang/Object; +PLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->getSerializedSize(Landroidx/datastore/preferences/protobuf/UnknownFieldSetLite;)I +PLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->getSerializedSize(Ljava/lang/Object;)I +PLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->writeTo(Landroidx/datastore/preferences/protobuf/UnknownFieldSetLite;Landroidx/datastore/preferences/protobuf/Writer;)V +PLandroidx/datastore/preferences/protobuf/UnknownFieldSetLiteSchema;->writeTo(Ljava/lang/Object;Landroidx/datastore/preferences/protobuf/Writer;)V +PLandroidx/datastore/preferences/protobuf/UnsafeUtil$Android64MemoryAccessor;->putByte(Ljava/lang/Object;JB)V +PLandroidx/datastore/preferences/protobuf/UnsafeUtil;->access$500(Ljava/lang/Object;JB)V +PLandroidx/datastore/preferences/protobuf/UnsafeUtil;->putByte([BJB)V +PLandroidx/datastore/preferences/protobuf/UnsafeUtil;->putByteLittleEndian(Ljava/lang/Object;JB)V +PLandroidx/datastore/preferences/protobuf/Utf8$SafeProcessor;->encodeUtf8(Ljava/lang/String;[BII)I +PLandroidx/datastore/preferences/protobuf/Utf8;->encode(Ljava/lang/String;[BII)I +PLandroidx/datastore/preferences/protobuf/Utf8;->encodedLength(Ljava/lang/String;)I +PLandroidx/datastore/preferences/protobuf/WireFormat$FieldType;->getWireType()I +PLandroidx/datastore/preferences/protobuf/WireFormat$JavaType;->values()[Landroidx/datastore/preferences/protobuf/WireFormat$JavaType; +PLandroidx/datastore/preferences/protobuf/Writer$FieldOrder;->()V +PLandroidx/datastore/preferences/protobuf/Writer$FieldOrder;->(Ljava/lang/String;I)V diff --git a/datastore/datastore-preferences/src/androidMain/baselineProfiles/baseline-prof.txt b/datastore/datastore-preferences/src/androidMain/baselineProfiles/baseline-prof.txt new file mode 100644 index 0000000000000..e114c7fbd8642 --- /dev/null +++ b/datastore/datastore-preferences/src/androidMain/baselineProfiles/baseline-prof.txt @@ -0,0 +1,109 @@ +# TODO(b/469127532): Remove this merged baseline profile once AGP adds support for multiple +# baseline profile files per baselineProfiles directory. + +HSPLandroidx/datastore/preferences/PreferenceDataStoreDelegateKt$$ExternalSyntheticLambda0;->()V +HSPLandroidx/datastore/preferences/PreferenceDataStoreDelegateKt$$ExternalSyntheticLambda0;->invoke(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/PreferenceDataStoreDelegateKt;->$r8$lambda$M8wy2jP_OP2ZhUCev6CMnhPDA50(Landroid/content/Context;)Ljava/util/List; +HSPLandroidx/datastore/preferences/PreferenceDataStoreDelegateKt;->preferencesDataStore$default(Ljava/lang/String;Landroidx/datastore/core/handlers/ReplaceFileCorruptionHandler;Lkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CoroutineScope;ILjava/lang/Object;)Lkotlin/properties/ReadOnlyProperty; +HSPLandroidx/datastore/preferences/PreferenceDataStoreDelegateKt;->preferencesDataStore$lambda$0(Landroid/content/Context;)Ljava/util/List; +HSPLandroidx/datastore/preferences/PreferenceDataStoreDelegateKt;->preferencesDataStore(Ljava/lang/String;Landroidx/datastore/core/handlers/ReplaceFileCorruptionHandler;Lkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CoroutineScope;)Lkotlin/properties/ReadOnlyProperty; +HSPLandroidx/datastore/preferences/PreferenceDataStoreFile;->preferencesDataStoreFile(Landroid/content/Context;Ljava/lang/String;)Ljava/io/File; +HSPLandroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate$$ExternalSyntheticLambda0;->(Landroid/content/Context;Landroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate;)V +HSPLandroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate$$ExternalSyntheticLambda0;->invoke()Ljava/lang/Object; +HSPLandroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate;->$r8$lambda$n_JROCQhFBE9lHuSpefRs1uNEcA(Landroid/content/Context;Landroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate;)Ljava/io/File; +HSPLandroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate;->(Ljava/lang/String;Landroidx/datastore/core/handlers/ReplaceFileCorruptionHandler;Lkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CoroutineScope;)V +HSPLandroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate;->getValue$lambda$0$0(Landroid/content/Context;Landroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate;)Ljava/io/File; +HSPLandroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate;->getValue(Landroid/content/Context;Lkotlin/reflect/KProperty;)Landroidx/datastore/core/DataStore; +HSPLandroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate;->getValue(Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/PreferencesProto$1;->()V +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder;->()V +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder;->(Landroidx/datastore/preferences/PreferencesProto$1;)V +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder;->putPreferences(Ljava/lang/String;Landroidx/datastore/preferences/PreferencesProto$Value;)Landroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder; +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap$PreferencesDefaultEntryHolder;->()V +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->()V +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->()V +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->access$000()Landroidx/datastore/preferences/PreferencesProto$PreferenceMap; +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->access$100(Landroidx/datastore/preferences/PreferencesProto$PreferenceMap;)Ljava/util/Map; +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->dynamicMethod(Landroidx/datastore/preferences/protobuf/GeneratedMessageLite$MethodToInvoke;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->getMutablePreferencesMap()Ljava/util/Map; +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->getPreferencesMap()Ljava/util/Map; +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->internalGetMutablePreferences()Landroidx/datastore/preferences/protobuf/MapFieldLite; +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->internalGetPreferences()Landroidx/datastore/preferences/protobuf/MapFieldLite; +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->newBuilder()Landroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder; +HSPLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->parseFrom(Ljava/io/InputStream;)Landroidx/datastore/preferences/PreferencesProto$PreferenceMap; +HSPLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->()V +HSPLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->(Landroidx/datastore/preferences/PreferencesProto$1;)V +HSPLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->setInteger(I)Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +HSPLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->setString(Ljava/lang/String;)Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +HSPLandroidx/datastore/preferences/PreferencesProto$Value$ValueCase;->$values()[Landroidx/datastore/preferences/PreferencesProto$Value$ValueCase; +HSPLandroidx/datastore/preferences/PreferencesProto$Value$ValueCase;->()V +HSPLandroidx/datastore/preferences/PreferencesProto$Value$ValueCase;->(Ljava/lang/String;II)V +HSPLandroidx/datastore/preferences/PreferencesProto$Value$ValueCase;->forNumber(I)Landroidx/datastore/preferences/PreferencesProto$Value$ValueCase; +HSPLandroidx/datastore/preferences/PreferencesProto$Value$ValueCase;->values()[Landroidx/datastore/preferences/PreferencesProto$Value$ValueCase; +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->()V +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->()V +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->access$1300(Landroidx/datastore/preferences/PreferencesProto$Value;Ljava/lang/String;)V +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->access$300()Landroidx/datastore/preferences/PreferencesProto$Value; +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->access$900(Landroidx/datastore/preferences/PreferencesProto$Value;I)V +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->dynamicMethod(Landroidx/datastore/preferences/protobuf/GeneratedMessageLite$MethodToInvoke;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->getBoolean()Z +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->getDefaultInstance()Landroidx/datastore/preferences/PreferencesProto$Value; +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->getDouble()D +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->getFloat()F +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->getInteger()I +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->getLong()J +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->getString()Ljava/lang/String; +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->getValueCase()Landroidx/datastore/preferences/PreferencesProto$Value$ValueCase; +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->newBuilder()Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->setInteger(I)V +HSPLandroidx/datastore/preferences/PreferencesProto$Value;->setString(Ljava/lang/String;)V +Landroidx/datastore/preferences/PreferenceDataStoreDelegateKt$$ExternalSyntheticLambda0; +Landroidx/datastore/preferences/PreferenceDataStoreDelegateKt; +Landroidx/datastore/preferences/PreferenceDataStoreFile; +Landroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate$$ExternalSyntheticLambda0; +Landroidx/datastore/preferences/PreferenceDataStoreSingletonDelegate; +Landroidx/datastore/preferences/PreferencesProto$1; +Landroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder; +Landroidx/datastore/preferences/PreferencesProto$PreferenceMap$PreferencesDefaultEntryHolder; +Landroidx/datastore/preferences/PreferencesProto$PreferenceMap; +Landroidx/datastore/preferences/PreferencesProto$PreferenceMapOrBuilder; +Landroidx/datastore/preferences/PreferencesProto$StringSet; +Landroidx/datastore/preferences/PreferencesProto$StringSetOrBuilder; +Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +Landroidx/datastore/preferences/PreferencesProto$Value$ValueCase; +Landroidx/datastore/preferences/PreferencesProto$Value; +Landroidx/datastore/preferences/PreferencesProto$ValueOrBuilder; +PLandroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder;->()V +PLandroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder;->(Landroidx/datastore/preferences/PreferencesProto$1;)V +PLandroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder;->putPreferences(Ljava/lang/String;Landroidx/datastore/preferences/PreferencesProto$Value;)Landroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder; +PLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->access$000()Landroidx/datastore/preferences/PreferencesProto$PreferenceMap; +PLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->access$100(Landroidx/datastore/preferences/PreferencesProto$PreferenceMap;)Ljava/util/Map; +PLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->getMutablePreferencesMap()Ljava/util/Map; +PLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->internalGetMutablePreferences()Landroidx/datastore/preferences/protobuf/MapFieldLite; +PLandroidx/datastore/preferences/PreferencesProto$PreferenceMap;->newBuilder()Landroidx/datastore/preferences/PreferencesProto$PreferenceMap$Builder; +PLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->()V +PLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->(Landroidx/datastore/preferences/PreferencesProto$1;)V +PLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->setBoolean(Z)Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +PLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->setDouble(D)Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +PLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->setFloat(F)Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +PLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->setInteger(I)Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +PLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->setLong(J)Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +PLandroidx/datastore/preferences/PreferencesProto$Value$Builder;->setString(Ljava/lang/String;)Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +PLandroidx/datastore/preferences/PreferencesProto$Value;->access$1100(Landroidx/datastore/preferences/PreferencesProto$Value;J)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->access$1300(Landroidx/datastore/preferences/PreferencesProto$Value;Ljava/lang/String;)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->access$1900(Landroidx/datastore/preferences/PreferencesProto$Value;D)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->access$300()Landroidx/datastore/preferences/PreferencesProto$Value; +PLandroidx/datastore/preferences/PreferencesProto$Value;->access$500(Landroidx/datastore/preferences/PreferencesProto$Value;Z)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->access$700(Landroidx/datastore/preferences/PreferencesProto$Value;F)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->access$900(Landroidx/datastore/preferences/PreferencesProto$Value;I)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->getDouble()D +PLandroidx/datastore/preferences/PreferencesProto$Value;->getFloat()F +PLandroidx/datastore/preferences/PreferencesProto$Value;->getInteger()I +PLandroidx/datastore/preferences/PreferencesProto$Value;->getLong()J +PLandroidx/datastore/preferences/PreferencesProto$Value;->newBuilder()Landroidx/datastore/preferences/PreferencesProto$Value$Builder; +PLandroidx/datastore/preferences/PreferencesProto$Value;->setBoolean(Z)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->setDouble(D)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->setFloat(F)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->setInteger(I)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->setLong(J)V +PLandroidx/datastore/preferences/PreferencesProto$Value;->setString(Ljava/lang/String;)V diff --git a/datastore/datastore/src/androidMain/baselineProfiles/baseline-prof.txt b/datastore/datastore/src/androidMain/baselineProfiles/baseline-prof.txt new file mode 100644 index 0000000000000..c31d203347539 --- /dev/null +++ b/datastore/datastore/src/androidMain/baselineProfiles/baseline-prof.txt @@ -0,0 +1,92 @@ +# TODO(b/469127532): Remove this merged baseline profile once AGP adds support for multiple +# baseline profile files per baselineProfiles directory. + +HSPLandroidx/datastore/DataStoreDelegateKt$$ExternalSyntheticLambda0;->()V +HSPLandroidx/datastore/DataStoreDelegateKt$$ExternalSyntheticLambda0;->invoke(Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/DataStoreDelegateKt;->$r8$lambda$ltH8eG9Ry63WYy-8nryGg0pFCWg(Landroid/content/Context;)Ljava/util/List; +HSPLandroidx/datastore/DataStoreDelegateKt;->dataStore$default(Ljava/lang/String;Landroidx/datastore/core/Serializer;Landroidx/datastore/core/handlers/ReplaceFileCorruptionHandler;Lkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CoroutineScope;ILjava/lang/Object;)Lkotlin/properties/ReadOnlyProperty; +HSPLandroidx/datastore/DataStoreDelegateKt;->dataStore$lambda$0(Landroid/content/Context;)Ljava/util/List; +HSPLandroidx/datastore/DataStoreDelegateKt;->dataStore(Ljava/lang/String;Landroidx/datastore/core/Serializer;Landroidx/datastore/core/handlers/ReplaceFileCorruptionHandler;Lkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CoroutineScope;)Lkotlin/properties/ReadOnlyProperty; +HSPLandroidx/datastore/DataStoreFile;->dataStoreFile(Landroid/content/Context;Ljava/lang/String;)Ljava/io/File; +HSPLandroidx/datastore/DataStoreSingletonDelegate$$ExternalSyntheticLambda0;->(Landroidx/datastore/DataStoreSingletonDelegate;Landroid/content/Context;)V +HSPLandroidx/datastore/DataStoreSingletonDelegate$$ExternalSyntheticLambda0;->invoke()Ljava/lang/Object; +HSPLandroidx/datastore/DataStoreSingletonDelegate;->$r8$lambda$jO2EpaGG7QjdydS-66DJO_jeR50(Landroidx/datastore/DataStoreSingletonDelegate;Landroid/content/Context;)Lokio/Path; +HSPLandroidx/datastore/DataStoreSingletonDelegate;->(Ljava/lang/String;Landroidx/datastore/core/okio/OkioSerializer;Landroidx/datastore/core/handlers/ReplaceFileCorruptionHandler;Lkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CoroutineScope;Z)V +HSPLandroidx/datastore/DataStoreSingletonDelegate;->getValue$lambda$0$0(Landroidx/datastore/DataStoreSingletonDelegate;Landroid/content/Context;)Lokio/Path; +HSPLandroidx/datastore/DataStoreSingletonDelegate;->getValue(Landroid/content/Context;Lkotlin/reflect/KProperty;)Landroidx/datastore/core/DataStore; +HSPLandroidx/datastore/DataStoreSingletonDelegate;->getValue(Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; +HSPLandroidx/datastore/OkioSerializerWrapper;->(Landroidx/datastore/core/Serializer;)V +HSPLandroidx/datastore/OkioSerializerWrapper;->getDefaultValue()Ljava/lang/Object; +HSPLandroidx/datastore/OkioSerializerWrapper;->readFrom(Lokio/BufferedSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/OkioSerializerWrapper;->writeTo(Ljava/lang/Object;Lokio/BufferedSink;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/AtomicBoolean;->(Z)V +HSPLandroidx/datastore/core/okio/AtomicBoolean;->get()Z +HSPLandroidx/datastore/core/okio/AtomicBoolean;->set(Z)V +HSPLandroidx/datastore/core/okio/OkioReadScope$readData$1;->(Landroidx/datastore/core/okio/OkioReadScope;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/okio/OkioReadScope;->(Lokio/FileSystem;Lokio/Path;Landroidx/datastore/core/okio/OkioSerializer;)V +HSPLandroidx/datastore/core/okio/OkioReadScope;->checkClose()V +HSPLandroidx/datastore/core/okio/OkioReadScope;->close()V +HSPLandroidx/datastore/core/okio/OkioReadScope;->getFileSystem()Lokio/FileSystem; +HSPLandroidx/datastore/core/okio/OkioReadScope;->getPath()Lokio/Path; +HSPLandroidx/datastore/core/okio/OkioReadScope;->getSerializer()Landroidx/datastore/core/okio/OkioSerializer; +HSPLandroidx/datastore/core/okio/OkioReadScope;->readData$suspendImpl(Landroidx/datastore/core/okio/OkioReadScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioReadScope;->readData(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda0;->(Landroidx/datastore/core/okio/OkioStorage;)V +HSPLandroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda1;->()V +HSPLandroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda2;->(Landroidx/datastore/core/okio/OkioStorage;)V +HSPLandroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda2;->invoke()Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioStorage$Companion;->()V +HSPLandroidx/datastore/core/okio/OkioStorage$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/okio/OkioStorage;->$r8$lambda$ngX1sQHRBFS9is12Tq2RMloe3b8(Landroidx/datastore/core/okio/OkioStorage;)Lokio/Path; +HSPLandroidx/datastore/core/okio/OkioStorage;->$r8$lambda$zaSHyDobNeO3yk4Wyl_gDVAF7jA(Lokio/Path;Lokio/FileSystem;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/okio/OkioStorage;->()V +HSPLandroidx/datastore/core/okio/OkioStorage;->(Lokio/FileSystem;Landroidx/datastore/core/okio/OkioSerializer;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;)V +HSPLandroidx/datastore/core/okio/OkioStorage;->(Lokio/FileSystem;Landroidx/datastore/core/okio/OkioSerializer;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +HSPLandroidx/datastore/core/okio/OkioStorage;->_init_$lambda$0(Lokio/Path;Lokio/FileSystem;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/okio/OkioStorage;->canonicalPath_delegate$lambda$0(Landroidx/datastore/core/okio/OkioStorage;)Lokio/Path; +HSPLandroidx/datastore/core/okio/OkioStorage;->createConnection()Landroidx/datastore/core/StorageConnection; +HSPLandroidx/datastore/core/okio/OkioStorage;->getCanonicalPath()Lokio/Path; +HSPLandroidx/datastore/core/okio/OkioStorageConnection$readScope$1;->(Landroidx/datastore/core/okio/OkioStorageConnection;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/okio/OkioStorageConnection$writeScope$1;->(Landroidx/datastore/core/okio/OkioStorageConnection;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/okio/OkioStorageConnection;->(Lokio/FileSystem;Lokio/Path;Landroidx/datastore/core/okio/OkioSerializer;Landroidx/datastore/core/InterProcessCoordinator;Lkotlin/jvm/functions/Function0;)V +HSPLandroidx/datastore/core/okio/OkioStorageConnection;->checkNotClosed()V +HSPLandroidx/datastore/core/okio/OkioStorageConnection;->getCoordinator()Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/okio/OkioStorageConnection;->readScope(Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioStorageConnection;->writeScope(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/OkioStorageKt;->createSingleProcessCoordinator(Lokio/Path;)Landroidx/datastore/core/InterProcessCoordinator; +HSPLandroidx/datastore/core/okio/OkioWriteScope$writeData$1;->(Landroidx/datastore/core/okio/OkioWriteScope;Lkotlin/coroutines/Continuation;)V +HSPLandroidx/datastore/core/okio/OkioWriteScope;->(Lokio/FileSystem;Lokio/Path;Landroidx/datastore/core/okio/OkioSerializer;)V +HSPLandroidx/datastore/core/okio/OkioWriteScope;->writeData(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +HSPLandroidx/datastore/core/okio/Synchronizer;->()V +Landroidx/datastore/DataStoreDelegateKt$$ExternalSyntheticLambda0; +Landroidx/datastore/DataStoreDelegateKt; +Landroidx/datastore/DataStoreFile; +Landroidx/datastore/DataStoreSingletonDelegate$$ExternalSyntheticLambda0; +Landroidx/datastore/DataStoreSingletonDelegate; +Landroidx/datastore/OkioSerializerWrapper; +Landroidx/datastore/core/okio/AtomicBoolean; +Landroidx/datastore/core/okio/OkioReadScope$readData$1; +Landroidx/datastore/core/okio/OkioReadScope; +Landroidx/datastore/core/okio/OkioSerializer; +Landroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda0; +Landroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda1; +Landroidx/datastore/core/okio/OkioStorage$$ExternalSyntheticLambda2; +Landroidx/datastore/core/okio/OkioStorage$Companion; +Landroidx/datastore/core/okio/OkioStorage; +Landroidx/datastore/core/okio/OkioStorageConnection$readScope$1; +Landroidx/datastore/core/okio/OkioStorageConnection$writeScope$1; +Landroidx/datastore/core/okio/OkioStorageConnection; +Landroidx/datastore/core/okio/OkioStorageKt; +Landroidx/datastore/core/okio/OkioWriteScope$writeData$1; +Landroidx/datastore/core/okio/OkioWriteScope; +Landroidx/datastore/core/okio/Synchronizer; +PLandroidx/datastore/OkioSerializerWrapper;->writeTo(Ljava/lang/Object;Lokio/BufferedSink;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/okio/OkioReadScope;->getFileSystem()Lokio/FileSystem; +PLandroidx/datastore/core/okio/OkioReadScope;->getPath()Lokio/Path; +PLandroidx/datastore/core/okio/OkioReadScope;->getSerializer()Landroidx/datastore/core/okio/OkioSerializer; +PLandroidx/datastore/core/okio/OkioStorageConnection$writeScope$1;->(Landroidx/datastore/core/okio/OkioStorageConnection;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/okio/OkioStorageConnection;->writeScope(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +PLandroidx/datastore/core/okio/OkioWriteScope$writeData$1;->(Landroidx/datastore/core/okio/OkioWriteScope;Lkotlin/coroutines/Continuation;)V +PLandroidx/datastore/core/okio/OkioWriteScope;->(Lokio/FileSystem;Lokio/Path;Landroidx/datastore/core/okio/OkioSerializer;)V +PLandroidx/datastore/core/okio/OkioWriteScope;->writeData(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; From 8648b3c176054ba1f5a04ba3d1255a20fb345f24 Mon Sep 17 00:00:00 2001 From: Aurimas Liutikas Date: Wed, 14 Jan 2026 14:18:16 -0800 Subject: [PATCH 17/19] Disabling remaining tests that fail on API 36 Bug: 460511639 Bug: 460508283 Bug: 454429920 Test: running these tests on emulator locally Change-Id: If4f2788c6865a65930b7f499a8fe67428e717b1a --- .../androidx/appcompat/app/BaseKeyEventsTestCase.kt | 12 +++--------- .../app/KeyEventsTestCaseWithWindowDecor.kt | 8 ++------ .../compose/material/ExposedDropdownMenuTest.kt | 2 ++ .../androidx/leanback/app/BrowseFragmentTest.java | 3 ++- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/BaseKeyEventsTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/BaseKeyEventsTestCase.kt index 37722e020696a..3c2fd67d02035 100644 --- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/BaseKeyEventsTestCase.kt +++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/BaseKeyEventsTestCase.kt @@ -42,6 +42,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.filters.MediumTest +import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import androidx.testutils.PollingCheck import androidx.testutils.withActivity @@ -51,7 +52,6 @@ import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull -import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -144,14 +144,11 @@ abstract class BaseKeyEventsTestCase(private val activityC } } + @SdkSuppress(maxSdkVersion = 35) // b/460511639 @Test @LargeTest @Throws(InterruptedException::class) fun testBackCollapsesActionView() { - assumeFalse( - "Test fails on cuttlefish b/460511639", - Build.MODEL.contains("Cuttlefish", ignoreCase = true), - ) with(ActivityScenario.launch(activityClass)) { // Click on the Search menu item onView(withId(R.id.action_search)).perform(click()) @@ -208,11 +205,8 @@ abstract class BaseKeyEventsTestCase(private val activityC @Test @MediumTest + @SdkSuppress(maxSdkVersion = 35) // b/460511639 fun testBackPressWithEmptyMenuHandledByActivity() { - assumeFalse( - "Test fails on cuttlefish b/460511639", - Build.MODEL.contains("Cuttlefish", ignoreCase = true), - ) with(ActivityScenario.launch(activityClass)) { // Pressing the menu key with an empty menu does nothing. val scenario = (this as? ActivityScenario)!! diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/KeyEventsTestCaseWithWindowDecor.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/KeyEventsTestCaseWithWindowDecor.kt index b71cec6ec0a7c..a02303b7542c3 100644 --- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/KeyEventsTestCaseWithWindowDecor.kt +++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/KeyEventsTestCaseWithWindowDecor.kt @@ -16,7 +16,6 @@ package androidx.appcompat.app import android.content.Context -import android.os.Build import android.view.KeyEvent import android.view.View import android.view.ViewGroup @@ -27,23 +26,20 @@ import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.pressKey import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.filters.LargeTest +import androidx.test.filters.SdkSuppress import androidx.testutils.PollingCheck import androidx.testutils.withActivity import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue -import org.junit.Assume.assumeFalse import org.junit.Test class KeyEventsTestCaseWithWindowDecor : BaseKeyEventsTestCase(WindowDecorAppCompatActivity::class.java) { + @SdkSuppress(maxSdkVersion = 35) // b/460511639 @Test @LargeTest @Throws(Throwable::class) fun testUnhandledKeys() { - assumeFalse( - "Test fails on cuttlefish b/460511639", - Build.MODEL.contains("Cuttlefish", ignoreCase = true), - ) with(ActivityScenario.launch(WindowDecorAppCompatActivity::class.java)) { val listener = MockUnhandledKeyListener() val mockView1: View = withActivity { HandlerView(this) } diff --git a/compose/material/material/src/androidDeviceTest/kotlin/androidx/compose/material/ExposedDropdownMenuTest.kt b/compose/material/material/src/androidDeviceTest/kotlin/androidx/compose/material/ExposedDropdownMenuTest.kt index 63817a0ce3d96..faf25c2a54af4 100644 --- a/compose/material/material/src/androidDeviceTest/kotlin/androidx/compose/material/ExposedDropdownMenuTest.kt +++ b/compose/material/material/src/androidDeviceTest/kotlin/androidx/compose/material/ExposedDropdownMenuTest.kt @@ -57,6 +57,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest +import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice @@ -109,6 +110,7 @@ class ExposedDropdownMenuTest { rule.onNodeWithTag(MenuItemTag).assertDoesNotExist() } + @SdkSuppress(maxSdkVersion = 35) // b/454429920 @Test fun expandedBehaviour_dismissesOnBackPress() { rule.setMaterialContent { diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/app/BrowseFragmentTest.java b/leanback/leanback/src/androidTest/java/androidx/leanback/app/BrowseFragmentTest.java index 10d4cfc6c6c58..45e34ff7ddfc3 100644 --- a/leanback/leanback/src/androidTest/java/androidx/leanback/app/BrowseFragmentTest.java +++ b/leanback/leanback/src/androidTest/java/androidx/leanback/app/BrowseFragmentTest.java @@ -53,6 +53,7 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; +import androidx.test.filters.SdkSuppress; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; @@ -126,9 +127,9 @@ public boolean canProceed() { }); } + @SdkSuppress(maxSdkVersion = 35) // b/460508283 @Test public void testTouchMode() throws Throwable { - assumeFalse("Test fails on cuttlefish b/460508283", Build.MODEL.contains("Cuttlefish")); Intent intent = new Intent(); intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true); intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY , 0L); From 0992b6a9416b8a4c9d9e329be601db8e4a560554 Mon Sep 17 00:00:00 2001 From: Jay Abi-Saad Date: Wed, 14 Jan 2026 18:21:24 -0500 Subject: [PATCH 18/19] Make JXR depend on new 0.0.11 version of Impress Change-Id: Ia9dccdc4f4a8df18684ce8262efded480ab776e6 --- xr/compose/compose-testing/build.gradle | 2 +- xr/compose/compose/build.gradle | 2 +- xr/scenecore/scenecore-spatial-rendering/build.gradle | 2 +- xr/scenecore/scenecore-testing/build.gradle | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/xr/compose/compose-testing/build.gradle b/xr/compose/compose-testing/build.gradle index 8fd584b97e969..bb1ca7f9fbc3c 100644 --- a/xr/compose/compose-testing/build.gradle +++ b/xr/compose/compose-testing/build.gradle @@ -48,7 +48,7 @@ dependencies { implementation("androidx.compose.ui:ui-unit:1.7.5") implementation("androidx.compose.ui:ui-util:1.7.5") implementation("androidx.compose.ui:ui-test-junit4:1.7.5") - implementation("com.google.ar:impress:0.0.10") + implementation("com.google.ar:impress:0.0.11") compileOnly(libs.androidExtensionsXr) compileOnly(files(new File(AndroidXConfig.getPrebuiltsRoot(project), "androidx/xr/extensions/com.android.extensions.xr.host.test.jar"))) diff --git a/xr/compose/compose/build.gradle b/xr/compose/compose/build.gradle index e36cfc6be6f9d..3d79d12e19bdc 100644 --- a/xr/compose/compose/build.gradle +++ b/xr/compose/compose/build.gradle @@ -66,7 +66,7 @@ dependencies { testImplementation("androidx.compose.ui:ui-test-junit4:1.7.5") testImplementation(project(":xr:compose:compose-testing")) testImplementation(project(":xr:scenecore:scenecore-testing")) - testImplementation("com.google.ar:impress:0.0.10") + testImplementation("com.google.ar:impress:0.0.11") compileOnly(libs.androidExtensionsXr) testImplementation(files(new File(AndroidXConfig.getPrebuiltsRoot(project), "androidx/xr/extensions/com.android.extensions.xr.host.test.jar"))) diff --git a/xr/scenecore/scenecore-spatial-rendering/build.gradle b/xr/scenecore/scenecore-spatial-rendering/build.gradle index a2c2a16b69dd1..a177a5f58aa7a 100644 --- a/xr/scenecore/scenecore-spatial-rendering/build.gradle +++ b/xr/scenecore/scenecore-spatial-rendering/build.gradle @@ -36,7 +36,7 @@ dependencies { api(project(":xr:scenecore:scenecore-runtime")) implementation("androidx.annotation:annotation:1.8.1") - implementation("com.google.ar:impress:0.0.10") + implementation("com.google.ar:impress:0.0.11") testImplementation(libs.junit) testImplementation(libs.kotlinTest) diff --git a/xr/scenecore/scenecore-testing/build.gradle b/xr/scenecore/scenecore-testing/build.gradle index 8894e314f82b0..31b0ff6f829d0 100644 --- a/xr/scenecore/scenecore-testing/build.gradle +++ b/xr/scenecore/scenecore-testing/build.gradle @@ -37,7 +37,7 @@ dependencies { api(project(":xr:arcore:arcore-testing")) api(project(":xr:scenecore:scenecore-runtime")) - implementation("com.google.ar:impress:0.0.10") + implementation("com.google.ar:impress:0.0.11") implementation(libs.testExtTruth) implementation("androidx.annotation:annotation:1.8.1") implementation("androidx.concurrent:concurrent-futures:1.0.0") From f263260c2520e6d912ef0cf7190552fa90ea0622 Mon Sep 17 00:00:00 2001 From: Tim Wong Date: Tue, 23 Dec 2025 18:06:29 +0800 Subject: [PATCH 19/19] Add JNI marshalling tests for ToggleGltfModelAnimation Implements the Java side of the JNI marshalling tests for the 'ToggleGltfModelAnimation' API. The corresponding native C++ changes are covered in CL/846641826. Bug: 471145916 Test: On the emulator Change-Id: I56ea85c688bc541400eacefafdfdadf5de8eaa46 --- .../impl/impress/GltfJniMarshallingTest.kt | 24 +++++++++++++++++++ .../impl/impress/ImpressApiTestHelper.java | 2 ++ 2 files changed, 26 insertions(+) diff --git a/xr/scenecore/scenecore-spatial-rendering/src/androidTest/java/androidx/xr/scenecore/impl/impress/GltfJniMarshallingTest.kt b/xr/scenecore/scenecore-spatial-rendering/src/androidTest/java/androidx/xr/scenecore/impl/impress/GltfJniMarshallingTest.kt index f6147b3034cb3..34be80b09cb6b 100644 --- a/xr/scenecore/scenecore-spatial-rendering/src/androidTest/java/androidx/xr/scenecore/impl/impress/GltfJniMarshallingTest.kt +++ b/xr/scenecore/scenecore-spatial-rendering/src/androidTest/java/androidx/xr/scenecore/impl/impress/GltfJniMarshallingTest.kt @@ -186,6 +186,30 @@ class GltfJniMarshallingTest : BaseJniMarshallingTest() { // This JNI call does not return any data, so the only assertion is on the native side. } + @Test + fun toggleGltfModelAnimation_marshalsParams_invokesOnPause() { + // Set toggle as false to pause the animation. + val expectedToggle = false + ImpressApiTestHelper.nativeSetExpectedToggleGltfModelAnimation(TEST_NODE_ID, expectedToggle) + val node = ImpressNode(TEST_NODE_ID) + + mImpressApi.toggleGltfModelAnimation(node, expectedToggle) + + // This JNI call does not return any data, so the only assertion is on the native side. + } + + @Test + fun toggleGltfModelAnimation_marshalsParams_invokesOnResume() { + // Set toggle as false to pause the animation. + val expectedToggle = true + ImpressApiTestHelper.nativeSetExpectedToggleGltfModelAnimation(TEST_NODE_ID, expectedToggle) + val node = ImpressNode(TEST_NODE_ID) + + mImpressApi.toggleGltfModelAnimation(node, expectedToggle) + + // This JNI call does not return any data, so the only assertion is on the native side. + } + @Test fun getGltfModelBoundingBox_marshalsNodeId_returnsBox() { val expectedCenter = floatArrayOf(1.0f, 2.0f, 3.0f) diff --git a/xr/scenecore/scenecore-spatial-rendering/src/androidTest/java/androidx/xr/scenecore/impl/impress/ImpressApiTestHelper.java b/xr/scenecore/scenecore-spatial-rendering/src/androidTest/java/androidx/xr/scenecore/impl/impress/ImpressApiTestHelper.java index 7d5c47f97891e..1c8240ffa0cdc 100644 --- a/xr/scenecore/scenecore-spatial-rendering/src/androidTest/java/androidx/xr/scenecore/impl/impress/ImpressApiTestHelper.java +++ b/xr/scenecore/scenecore-spatial-rendering/src/androidTest/java/androidx/xr/scenecore/impl/impress/ImpressApiTestHelper.java @@ -57,6 +57,8 @@ static native void nativeSetExpectedAnimateGltfModel( static native void nativeSetExpectedStopGltfModelAnimation(int nodeId); + static native void nativeSetExpectedToggleGltfModelAnimation(int nodeId, boolean toggle); + static native void nativeSetExpectedGetGltfModelLocalBounds(int nodeId); static native void nativeSetGetGltfModelLocalBoundsSuccess(float[] center, float[] halfExtents);