Merge "Upgrade ConstraintLayout to alpha 14" into androidx-main
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 464914e..c91b733 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -293,7 +293,7 @@
task: AbstractTestTask,
anchorTask: Task,
) {
- if (task.name !in listOf("linuxx64StubsTest", "jvmStubsTest")) anchorTask.dependsOn(task)
+ if (task.name !in listOf("jvmStubsTest")) anchorTask.dependsOn(task)
val ignoreFailuresProperty =
project.providers.gradleProperty(TEST_FAILURES_DO_NOT_FAIL_TEST_TASK)
val ignoreFailures = ignoreFailuresProperty.isPresent
@@ -670,6 +670,9 @@
}
project.setUpCheckDocsTask(androidXExtension)
project.writeBlankPublicTxtToAar(kotlinMultiplatformAndroidComponentsExtension)
+ kotlinMultiplatformAndroidComponentsExtension.onVariant {
+ project.validateKotlinModuleFiles(it.name, it.artifacts.get(SingleArtifact.AAR))
+ }
}
private fun Project.writeBlankPublicTxtToAar(
@@ -841,6 +844,10 @@
onVariants { variant ->
variant.configureTests()
variant.enableLongMethodTracingInMicrobenchmark(project)
+ project.validateKotlinModuleFiles(
+ variant.name,
+ variant.artifacts.get(SingleArtifact.AAR)
+ )
}
}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ValidateKotlinModuleFiles.kt b/buildSrc/private/src/main/kotlin/androidx/build/ValidateKotlinModuleFiles.kt
new file mode 100644
index 0000000..e9ece07
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ValidateKotlinModuleFiles.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2024 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.build
+
+import androidx.build.uptodatedness.cacheEvenIfNoOutputs
+import com.android.SdkConstants.DOT_KOTLIN_MODULE
+import com.android.utils.appendCapitalized
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.file.ArchiveOperations
+import org.gradle.api.file.RegularFile
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
+import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
+
+internal fun Project.validateKotlinModuleFiles(variantName: String, aar: Provider<RegularFile>) {
+ if (
+ !project.plugins.hasPlugin(KotlinBasePluginWrapper::class.java) &&
+ !project.plugins.hasPlugin(KotlinMultiplatformPluginWrapper::class.java)
+ ) {
+ return
+ }
+ val validateKotlinModuleFiles =
+ tasks.register(
+ "validateKotlinModuleFilesFor".appendCapitalized(variantName),
+ ValidateModuleFilesTask::class.java
+ ) {
+ it.aar.set(aar)
+ it.cacheEvenIfNoOutputs()
+ }
+ project.addToBuildOnServer(validateKotlinModuleFiles)
+}
+
+@CacheableTask
+abstract class ValidateModuleFilesTask() : DefaultTask() {
+
+ @get:Inject abstract val archiveOperations: ArchiveOperations
+
+ @get:PathSensitive(PathSensitivity.NONE) @get:InputFile abstract val aar: RegularFileProperty
+
+ @get:Internal
+ val fileName: String
+ get() = aar.get().asFile.name
+
+ @TaskAction
+ fun execute() {
+ val fileTree = archiveOperations.zipTree(aar)
+ val classesJar =
+ fileTree.find { it.name == "classes.jar" }
+ ?: throw GradleException("Could not classes.jar in $fileName")
+ val jarContents = archiveOperations.zipTree(classesJar)
+ if (jarContents.files.size <= 1) {
+ // only version file, stub project with no sources.
+ return
+ }
+ jarContents.find { it.name.endsWith(DOT_KOTLIN_MODULE) }
+ ?: throw GradleException("Could not find .kotlin_module file in $fileName")
+ }
+}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index d326de5..d2b1913 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -963,6 +963,23 @@
}
}
+ @Test
+ fun canSetAudioSource() {
+ // Arrange.
+ val recorder = createRecorder(audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION)
+
+ // Assert.
+ assertThat(recorder.audioSource).isEqualTo(MediaRecorder.AudioSource.VOICE_RECOGNITION)
+
+ // Act: ensure the value is correctly propagated to the internal AudioSource instance.
+ // Start recording to create the AudioSource instance.
+ recordingSession.createRecording(recorder = recorder).startAndVerify(statusCount = 1)
+
+ // Assert.
+ assertThat(recorder.mAudioSource.mAudioSource)
+ .isEqualTo(MediaRecorder.AudioSource.VOICE_RECOGNITION)
+ }
+
private fun testRecorderIsConfiguredBasedOnTargetVideoEncodingBitrate(targetBitrate: Int) {
// Arrange.
val recorder = createRecorder(targetBitrate = targetBitrate)
@@ -1000,6 +1017,7 @@
targetBitrate: Int? = null,
retrySetupVideoMaxCount: Int? = null,
retrySetupVideoDelayMs: Long? = null,
+ audioSource: Int? = null,
): Recorder {
val recorder =
Recorder.Builder()
@@ -1009,7 +1027,8 @@
executor?.let { setExecutor(it) }
videoEncoderFactory?.let { setVideoEncoderFactory(it) }
audioEncoderFactory?.let { setAudioEncoderFactory(it) }
- targetBitrate?.let { setTargetVideoEncodingBitRate(targetBitrate) }
+ targetBitrate?.let { setTargetVideoEncodingBitRate(it) }
+ audioSource?.let { setAudioSource(it) }
}
.build()
.apply {
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
index 4d40ce9..6cb8513 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -66,6 +66,7 @@
import androidx.camera.testing.impl.getRotation
import androidx.camera.testing.impl.mocks.MockScreenFlash
import androidx.camera.testing.impl.useAndRelease
+import androidx.camera.testing.impl.video.Recording
import androidx.camera.testing.impl.video.RecordingSession
import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE
import androidx.lifecycle.LifecycleOwner
@@ -403,13 +404,16 @@
// Act: Ensure the Recorder is initialized before start test.
recordingSession.createRecording().startAndVerify().stop()
- instrumentation.runOnMainSync { lifecycleOwner.pauseAndStop() }
- recordingSession.createRecording().apply {
- start()
+ lateinit var recording: Recording
+ instrumentation.runOnMainSync {
+ lifecycleOwner.pauseAndStop()
- // Verify.
- verifyFinalize(error = ERROR_SOURCE_INACTIVE)
+ // TODO(b/353578694): call start() in main thread to workaround the race condition.
+ recording = recordingSession.createRecording().start()
}
+
+ // Verify.
+ recording.verifyFinalize(error = ERROR_SOURCE_INACTIVE)
}
@Test
@@ -941,8 +945,7 @@
)
checkAndBindUseCases(preview, videoCapture)
- val recording =
- recordingSession.createRecording(asPersistentRecording = true).startAndVerify()
+ recordingSession.createRecording(asPersistentRecording = true).startAndVerify()
// Act.
instrumentation.runOnMainSync { lifecycleOwner.pauseAndStop() }
@@ -951,15 +954,6 @@
false,
"Lifecycle stopped but camera still in video usage"
)
-
- // Clean-up.
- // TODO(b/353113961): To avoid audio codec leak, resume lifecycle then stop the recording.
- instrumentation.runOnMainSync { lifecycleOwner.startAndResume() }
- // Delay a bit by checking status to avoid crash as the stack in b/342977497.
- recording.verifyStatus()
- recording.stopAndVerify()
-
- Unit
}
// TODO: b/341691683 - Add tests for multiple VideoCapture bound and recording concurrently
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
index aee88cb..1250761 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
@@ -701,8 +701,9 @@
* create this recorder, or the default value of {@link AudioSpec#SOURCE_AUTO} if no source was
* set.
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
@AudioSpec.Source
- int getAudioSource() {
+ public int getAudioSource() {
return getObservableData(mMediaSpec).getAudioSpec().getSource();
}
@@ -3553,8 +3554,9 @@
* {@link AudioSpec#SOURCE_CAMCORDER}. Default is
* {@link AudioSpec#SOURCE_AUTO}.
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
@NonNull
- Builder setAudioSource(@AudioSpec.Source int source) {
+ public Builder setAudioSource(@AudioSpec.Source int source) {
mMediaSpecBuilder.configureAudio(builder -> builder.setSource(source));
return this;
}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/AudioSource.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/AudioSource.java
index 70e0f58..38cda9f 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/AudioSource.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/AudioSource.java
@@ -144,6 +144,8 @@
double mAudioAmplitude;
long mAmplitudeTimestamp = 0;
private final int mAudioFormat;
+ @VisibleForTesting
+ public final int mAudioSource;
/**
* Creates an AudioSource for the given settings.
@@ -192,6 +194,7 @@
mAudioStream.setCallback(new AudioStreamCallback(), mExecutor);
mSilentAudioStream = new SilentAudioStream(settings);
mAudioFormat = settings.getAudioFormat();
+ mAudioSource = settings.getAudioSource();
}
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
diff --git a/car/app/app-testing/api/1.7.0-beta01.txt b/car/app/app-testing/api/1.7.0-beta01.txt
index 57cf025..b478e9b 100644
--- a/car/app/app-testing/api/1.7.0-beta01.txt
+++ b/car/app/app-testing/api/1.7.0-beta01.txt
@@ -42,6 +42,12 @@
method public java.util.List<java.lang.String!> getPermissionsRequested();
}
+ @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class TestDelegateInvoker {
+ method public <T> java.util.List<T> requestAllItemsForTest(androidx.car.app.serialization.ListDelegate<? extends T>);
+ method public <T> java.util.List<T> requestItemRangeForTest(androidx.car.app.serialization.ListDelegate<? extends T>, int startIndex, int endIndex);
+ field public static final androidx.car.app.testing.TestDelegateInvoker INSTANCE;
+ }
+
public class TestScreenManager extends androidx.car.app.ScreenManager {
method public java.util.List<androidx.car.app.Screen!> getScreensPushed();
method public java.util.List<androidx.car.app.Screen!> getScreensRemoved();
diff --git a/car/app/app-testing/api/current.txt b/car/app/app-testing/api/current.txt
index 57cf025..b478e9b 100644
--- a/car/app/app-testing/api/current.txt
+++ b/car/app/app-testing/api/current.txt
@@ -42,6 +42,12 @@
method public java.util.List<java.lang.String!> getPermissionsRequested();
}
+ @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class TestDelegateInvoker {
+ method public <T> java.util.List<T> requestAllItemsForTest(androidx.car.app.serialization.ListDelegate<? extends T>);
+ method public <T> java.util.List<T> requestItemRangeForTest(androidx.car.app.serialization.ListDelegate<? extends T>, int startIndex, int endIndex);
+ field public static final androidx.car.app.testing.TestDelegateInvoker INSTANCE;
+ }
+
public class TestScreenManager extends androidx.car.app.ScreenManager {
method public java.util.List<androidx.car.app.Screen!> getScreensPushed();
method public java.util.List<androidx.car.app.Screen!> getScreensRemoved();
diff --git a/car/app/app-testing/api/restricted_1.7.0-beta01.txt b/car/app/app-testing/api/restricted_1.7.0-beta01.txt
index 57cf025..b478e9b 100644
--- a/car/app/app-testing/api/restricted_1.7.0-beta01.txt
+++ b/car/app/app-testing/api/restricted_1.7.0-beta01.txt
@@ -42,6 +42,12 @@
method public java.util.List<java.lang.String!> getPermissionsRequested();
}
+ @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class TestDelegateInvoker {
+ method public <T> java.util.List<T> requestAllItemsForTest(androidx.car.app.serialization.ListDelegate<? extends T>);
+ method public <T> java.util.List<T> requestItemRangeForTest(androidx.car.app.serialization.ListDelegate<? extends T>, int startIndex, int endIndex);
+ field public static final androidx.car.app.testing.TestDelegateInvoker INSTANCE;
+ }
+
public class TestScreenManager extends androidx.car.app.ScreenManager {
method public java.util.List<androidx.car.app.Screen!> getScreensPushed();
method public java.util.List<androidx.car.app.Screen!> getScreensRemoved();
diff --git a/car/app/app-testing/api/restricted_current.txt b/car/app/app-testing/api/restricted_current.txt
index 57cf025..b478e9b 100644
--- a/car/app/app-testing/api/restricted_current.txt
+++ b/car/app/app-testing/api/restricted_current.txt
@@ -42,6 +42,12 @@
method public java.util.List<java.lang.String!> getPermissionsRequested();
}
+ @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public final class TestDelegateInvoker {
+ method public <T> java.util.List<T> requestAllItemsForTest(androidx.car.app.serialization.ListDelegate<? extends T>);
+ method public <T> java.util.List<T> requestItemRangeForTest(androidx.car.app.serialization.ListDelegate<? extends T>, int startIndex, int endIndex);
+ field public static final androidx.car.app.testing.TestDelegateInvoker INSTANCE;
+ }
+
public class TestScreenManager extends androidx.car.app.ScreenManager {
method public java.util.List<androidx.car.app.Screen!> getScreensPushed();
method public java.util.List<androidx.car.app.Screen!> getScreensRemoved();
diff --git a/car/app/app-testing/build.gradle b/car/app/app-testing/build.gradle
index 9220169..5ec3aa7 100644
--- a/car/app/app-testing/build.gradle
+++ b/car/app/app-testing/build.gradle
@@ -26,15 +26,19 @@
plugins {
id("AndroidXPlugin")
id("com.android.library")
+ id("org.jetbrains.kotlin.android")
}
dependencies {
api(project(":car:app:app"))
+
implementation "androidx.lifecycle:lifecycle-runtime:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation 'androidx.annotation:annotation:1.1.0'
implementation(libs.robolectric)
implementation("androidx.annotation:annotation-experimental:1.4.1")
+ api(libs.kotlinStdlib)
+ implementation(libs.kotlinStdlibCommon)
testImplementation(project(":car:app:app-projected"))
testImplementation(libs.junit)
diff --git a/car/app/app-testing/src/main/java/androidx/car/app/testing/TestDelegateInvoker.kt b/car/app/app-testing/src/main/java/androidx/car/app/testing/TestDelegateInvoker.kt
new file mode 100644
index 0000000..44660ce
--- /dev/null
+++ b/car/app/app-testing/src/main/java/androidx/car/app/testing/TestDelegateInvoker.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 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.
+ */
+
+/*
+ * Copyright (C) 2024 Google Inc.
+ *
+ * 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.car.app.testing
+
+import android.annotation.SuppressLint
+import androidx.car.app.OnDoneCallback
+import androidx.car.app.annotations.ExperimentalCarApi
+import androidx.car.app.serialization.Bundleable
+import androidx.car.app.serialization.ListDelegate
+
+/** Provides a simplified interface for invoking AIDL/delegate APIs in tests */
+@SuppressLint("NullAnnotationGroup")
+@ExperimentalCarApi
+public object TestDelegateInvoker {
+ public fun <T> ListDelegate<T>.requestAllItemsForTest(): List<T> =
+ requestItemRangeForTest(0, size - 1)
+
+ public fun <T> ListDelegate<T>.requestItemRangeForTest(
+ startIndex: Int,
+ endIndex: Int
+ ): List<T> = runForResult {
+ [email protected](startIndex, endIndex, it)
+ }
+
+ private fun <T> runForResult(f: (OnDoneCallback) -> Unit): T {
+ val callback = ResponseCapturingOnDoneCallback<T>()
+ f(callback)
+
+ // Generally, tests run in a single process.
+ // Therefore, Host/Client AIDL logic runs synchronously.
+ // Therefore, we assume the callback was fulfilled, without waiting.
+ return callback.getResponseOrCrash()
+ }
+
+ /**
+ * [OnDoneCallback] implementation for testing
+ *
+ * This class captures and stores the [Bundleable] response (if any), and unmarshalls it to the
+ * specified type.
+ */
+ private class ResponseCapturingOnDoneCallback<TResponse> : OnDoneCallback {
+ // "null" is a valid response
+ private var hasResponse = false
+ private var response: Bundleable? = null
+
+ override fun onSuccess(response: Bundleable?) {
+ check(!hasResponse) {
+ "Callback was invoked multiple times. Please create a new callback for each API call."
+ }
+ hasResponse = true
+ this.response = response
+ }
+
+ override fun onFailure(response: Bundleable) {
+ error("OnDone callbacks should never fail in tests")
+ }
+
+ fun getResponseOrCrash(): TResponse {
+ check(hasResponse) { "Callback was never invoked." }
+
+ @Suppress("UNCHECKED_CAST") return response?.get() as TResponse
+ }
+ }
+}
diff --git a/car/app/app-testing/src/test/java/androidx/car/app/testing/TestDelegateInvokerTest.kt b/car/app/app-testing/src/test/java/androidx/car/app/testing/TestDelegateInvokerTest.kt
new file mode 100644
index 0000000..5e3374f
--- /dev/null
+++ b/car/app/app-testing/src/test/java/androidx/car/app/testing/TestDelegateInvokerTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 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.car.app.testing
+
+import androidx.car.app.serialization.ListDelegateImpl
+import androidx.car.app.testing.TestDelegateInvoker.requestAllItemsForTest
+import androidx.car.app.testing.TestDelegateInvoker.requestItemRangeForTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.internal.DoNotInstrument
+
+/** Tests for {@link TestDelegateInvoker}. */
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+class TestDelegateInvokerTest {
+ @Test
+ fun requestAllItemsForTest() {
+ val numbers = List(10) { it }
+ val listDelegate = ListDelegateImpl(numbers)
+
+ assertThat(listDelegate.requestAllItemsForTest()).containsExactlyElementsIn(numbers)
+ }
+
+ @Test
+ fun requestItemRangeForTest() {
+ val numbers = List(10) { it }
+ val listDelegate = ListDelegateImpl(numbers)
+
+ assertThat(listDelegate.requestItemRangeForTest(3, 5))
+ .containsExactlyElementsIn(numbers.subList(3, 6)) // indices 3, 4, 5
+ }
+}
diff --git a/car/app/app/api/1.7.0-beta01.txt b/car/app/app/api/1.7.0-beta01.txt
index 958295c..3b4c1e5 100644
--- a/car/app/app/api/1.7.0-beta01.txt
+++ b/car/app/app/api/1.7.0-beta01.txt
@@ -1350,7 +1350,6 @@
@SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class GridSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.GridItem!,androidx.car.app.model.GridSection.Builder!> {
ctor public GridSection.Builder();
- ctor public GridSection.Builder(androidx.car.app.model.GridSection);
method public androidx.car.app.model.GridSection build();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemImageShape(int);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemSize(int);
@@ -1694,7 +1693,6 @@
@SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class RowSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.Row!,androidx.car.app.model.RowSection.Builder!> {
ctor public RowSection.Builder();
- ctor public RowSection.Builder(androidx.car.app.model.RowSection);
method public androidx.car.app.model.RowSection build();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder clearSelectionGroup();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder setAsSelectionGroup(int);
@@ -1736,14 +1734,13 @@
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public abstract class Section<T extends androidx.car.app.model.Item> {
ctor protected Section();
ctor protected Section(androidx.car.app.model.Section.BaseBuilder<T!,? extends java.lang.Object!>);
- method public java.util.List<T!> getItems();
+ method public androidx.car.app.serialization.ListDelegate<T!> getItemsDelegate();
method public androidx.car.app.model.CarText? getNoItemsMessage();
method public androidx.car.app.model.CarText? getTitle();
}
protected abstract static class Section.BaseBuilder<T extends androidx.car.app.model.Item, B> {
ctor protected Section.BaseBuilder();
- ctor protected Section.BaseBuilder(androidx.car.app.model.Section<T!>);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B addItem(T);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B clearItems();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setItems(java.util.List<T!>);
@@ -2384,6 +2381,12 @@
ctor public BundlerException(String?, Throwable);
}
+ @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public interface ListDelegate<T> {
+ method public int getSize();
+ method public void requestItemRange(int startIndex, int endIndex, androidx.car.app.OnDoneCallback callback);
+ property public abstract int size;
+ }
+
}
package androidx.car.app.suggestion {
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index 958295c..3b4c1e5 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -1350,7 +1350,6 @@
@SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class GridSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.GridItem!,androidx.car.app.model.GridSection.Builder!> {
ctor public GridSection.Builder();
- ctor public GridSection.Builder(androidx.car.app.model.GridSection);
method public androidx.car.app.model.GridSection build();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemImageShape(int);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemSize(int);
@@ -1694,7 +1693,6 @@
@SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class RowSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.Row!,androidx.car.app.model.RowSection.Builder!> {
ctor public RowSection.Builder();
- ctor public RowSection.Builder(androidx.car.app.model.RowSection);
method public androidx.car.app.model.RowSection build();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder clearSelectionGroup();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder setAsSelectionGroup(int);
@@ -1736,14 +1734,13 @@
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public abstract class Section<T extends androidx.car.app.model.Item> {
ctor protected Section();
ctor protected Section(androidx.car.app.model.Section.BaseBuilder<T!,? extends java.lang.Object!>);
- method public java.util.List<T!> getItems();
+ method public androidx.car.app.serialization.ListDelegate<T!> getItemsDelegate();
method public androidx.car.app.model.CarText? getNoItemsMessage();
method public androidx.car.app.model.CarText? getTitle();
}
protected abstract static class Section.BaseBuilder<T extends androidx.car.app.model.Item, B> {
ctor protected Section.BaseBuilder();
- ctor protected Section.BaseBuilder(androidx.car.app.model.Section<T!>);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B addItem(T);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B clearItems();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setItems(java.util.List<T!>);
@@ -2384,6 +2381,12 @@
ctor public BundlerException(String?, Throwable);
}
+ @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public interface ListDelegate<T> {
+ method public int getSize();
+ method public void requestItemRange(int startIndex, int endIndex, androidx.car.app.OnDoneCallback callback);
+ property public abstract int size;
+ }
+
}
package androidx.car.app.suggestion {
diff --git a/car/app/app/api/restricted_1.7.0-beta01.txt b/car/app/app/api/restricted_1.7.0-beta01.txt
index 958295c..3b4c1e5 100644
--- a/car/app/app/api/restricted_1.7.0-beta01.txt
+++ b/car/app/app/api/restricted_1.7.0-beta01.txt
@@ -1350,7 +1350,6 @@
@SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class GridSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.GridItem!,androidx.car.app.model.GridSection.Builder!> {
ctor public GridSection.Builder();
- ctor public GridSection.Builder(androidx.car.app.model.GridSection);
method public androidx.car.app.model.GridSection build();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemImageShape(int);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemSize(int);
@@ -1694,7 +1693,6 @@
@SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class RowSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.Row!,androidx.car.app.model.RowSection.Builder!> {
ctor public RowSection.Builder();
- ctor public RowSection.Builder(androidx.car.app.model.RowSection);
method public androidx.car.app.model.RowSection build();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder clearSelectionGroup();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder setAsSelectionGroup(int);
@@ -1736,14 +1734,13 @@
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public abstract class Section<T extends androidx.car.app.model.Item> {
ctor protected Section();
ctor protected Section(androidx.car.app.model.Section.BaseBuilder<T!,? extends java.lang.Object!>);
- method public java.util.List<T!> getItems();
+ method public androidx.car.app.serialization.ListDelegate<T!> getItemsDelegate();
method public androidx.car.app.model.CarText? getNoItemsMessage();
method public androidx.car.app.model.CarText? getTitle();
}
protected abstract static class Section.BaseBuilder<T extends androidx.car.app.model.Item, B> {
ctor protected Section.BaseBuilder();
- ctor protected Section.BaseBuilder(androidx.car.app.model.Section<T!>);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B addItem(T);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B clearItems();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setItems(java.util.List<T!>);
@@ -2384,6 +2381,12 @@
ctor public BundlerException(String?, Throwable);
}
+ @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public interface ListDelegate<T> {
+ method public int getSize();
+ method public void requestItemRange(int startIndex, int endIndex, androidx.car.app.OnDoneCallback callback);
+ property public abstract int size;
+ }
+
}
package androidx.car.app.suggestion {
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index 958295c..3b4c1e5 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -1350,7 +1350,6 @@
@SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class GridSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.GridItem!,androidx.car.app.model.GridSection.Builder!> {
ctor public GridSection.Builder();
- ctor public GridSection.Builder(androidx.car.app.model.GridSection);
method public androidx.car.app.model.GridSection build();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemImageShape(int);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.GridSection.Builder setItemSize(int);
@@ -1694,7 +1693,6 @@
@SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public static final class RowSection.Builder extends androidx.car.app.model.Section.BaseBuilder<androidx.car.app.model.Row!,androidx.car.app.model.RowSection.Builder!> {
ctor public RowSection.Builder();
- ctor public RowSection.Builder(androidx.car.app.model.RowSection);
method public androidx.car.app.model.RowSection build();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder clearSelectionGroup();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public androidx.car.app.model.RowSection.Builder setAsSelectionGroup(int);
@@ -1736,14 +1734,13 @@
@SuppressCompatibility @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public abstract class Section<T extends androidx.car.app.model.Item> {
ctor protected Section();
ctor protected Section(androidx.car.app.model.Section.BaseBuilder<T!,? extends java.lang.Object!>);
- method public java.util.List<T!> getItems();
+ method public androidx.car.app.serialization.ListDelegate<T!> getItemsDelegate();
method public androidx.car.app.model.CarText? getNoItemsMessage();
method public androidx.car.app.model.CarText? getTitle();
}
protected abstract static class Section.BaseBuilder<T extends androidx.car.app.model.Item, B> {
ctor protected Section.BaseBuilder();
- ctor protected Section.BaseBuilder(androidx.car.app.model.Section<T!>);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B addItem(T);
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B clearItems();
method @com.google.errorprone.annotations.CanIgnoreReturnValue public B setItems(java.util.List<T!>);
@@ -2384,6 +2381,12 @@
ctor public BundlerException(String?, Throwable);
}
+ @SuppressCompatibility @androidx.car.app.annotations.ExperimentalCarApi public interface ListDelegate<T> {
+ method public int getSize();
+ method public void requestItemRange(int startIndex, int endIndex, androidx.car.app.OnDoneCallback callback);
+ property public abstract int size;
+ }
+
}
package androidx.car.app.suggestion {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/GridSection.java b/car/app/app/src/main/java/androidx/car/app/model/GridSection.java
index 9718f2a..6f25eb1 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/GridSection.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/GridSection.java
@@ -160,13 +160,6 @@
super();
}
- /** Create a new {@link GridSection} builder, copying values from an existing instance. */
- public Builder(@NonNull GridSection gridSection) {
- super(gridSection);
- mItemSize = gridSection.mItemSize;
- mItemImageShape = gridSection.mItemImageShape;
- }
-
/** Sets the size of the items within this section. */
@NonNull
@CanIgnoreReturnValue
diff --git a/car/app/app/src/main/java/androidx/car/app/model/RowSection.java b/car/app/app/src/main/java/androidx/car/app/model/RowSection.java
index 7603d2a..a2220d0 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/RowSection.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/RowSection.java
@@ -51,7 +51,8 @@
}
/**
- * When set to a value that correlates to an index in {@link #getItems()}, this entire row
+ * When set to a value that correlates to an index in {@link #getItemsDelegate()}, this
+ * entire row
* section should be treated as a selection group (eg. radio group). Otherwise this will be a
* negative value to denote that this row section should not be transformed into a selection
* group.
@@ -119,12 +120,6 @@
super();
}
- /** Create a new {@link RowSection} builder, copying values from an existing instance. */
- public Builder(@NonNull RowSection rowSection) {
- super(rowSection);
- mInitialSelectedIndex = rowSection.mInitialSelectedIndex;
- }
-
/**
* Sets this entire {@link RowSection} as a selection group when passed a non-negative
* integer correlating to a valid index within the list of items added. The UI behaves
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Section.java b/car/app/app/src/main/java/androidx/car/app/model/Section.java
index e000a0d..212006b 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Section.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Section.java
@@ -22,6 +22,8 @@
import androidx.car.app.annotations.ExperimentalCarApi;
import androidx.car.app.annotations.KeepFields;
import androidx.car.app.model.constraints.CarTextConstraints;
+import androidx.car.app.serialization.ListDelegate;
+import androidx.car.app.serialization.ListDelegateImpl;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
@@ -41,7 +43,7 @@
@ExperimentalCarApi
public abstract class Section<T extends Item> {
@NonNull
- private final List<T> mItems;
+ private final ListDelegate<T> mItemsDelegate;
@Nullable
private final CarText mTitle;
@Nullable
@@ -49,22 +51,22 @@
// Empty constructor for serialization
protected Section() {
- mItems = Collections.emptyList();
+ mItemsDelegate = new ListDelegateImpl<>(Collections.emptyList());
mTitle = null;
mNoItemsMessage = null;
}
/** Constructor that fills out fields from any section builder. */
protected Section(@NonNull BaseBuilder<T, ?> builder) {
- mItems = Collections.unmodifiableList(builder.mItems);
+ mItemsDelegate = new ListDelegateImpl<>(Collections.unmodifiableList(builder.mItems));
mTitle = builder.mHeader;
mNoItemsMessage = builder.mNoItemsMessage;
}
/** Returns the items added to this section. */
@NonNull
- public List<T> getItems() {
- return mItems;
+ public ListDelegate<T> getItemsDelegate() {
+ return mItemsDelegate;
}
/** Returns the optional text that should appear with the items in this section. */
@@ -91,20 +93,20 @@
}
Section<?> section = (Section<?>) other;
- return Objects.equals(mItems, section.mItems) && Objects.equals(mTitle, section.mTitle)
+ return Objects.equals(mItemsDelegate, section.mItemsDelegate) && Objects.equals(mTitle,
+ section.mTitle)
&& Objects.equals(mNoItemsMessage, section.mNoItemsMessage);
}
@Override
public int hashCode() {
- return Objects.hash(mItems, mTitle, mNoItemsMessage);
+ return Objects.hash(mItemsDelegate, mTitle, mNoItemsMessage);
}
@NonNull
@Override
public String toString() {
- return "Section { items: " + mItems + ", title: " + mTitle + ", noItemsMessage: "
- + mNoItemsMessage + " }";
+ return "Section";
}
/**
@@ -125,12 +127,6 @@
protected BaseBuilder() {
}
- protected BaseBuilder(@NonNull Section<T> section) {
- mItems = section.mItems;
- mHeader = section.mTitle;
- mNoItemsMessage = section.mNoItemsMessage;
- }
-
/** Sets the items for this section, overwriting any other previously set items. */
@NonNull
@CanIgnoreReturnValue
@@ -175,7 +171,7 @@
CarText carText = CarText.create(title);
CarTextConstraints.TEXT_ONLY.validateOrThrow(carText);
mHeader = carText;
- return (B) this;
+ return (B) this;
}
/**
diff --git a/car/app/app/src/main/java/androidx/car/app/serialization/ListDelegate.kt b/car/app/app/src/main/java/androidx/car/app/serialization/ListDelegate.kt
index 8e87b8c..4e99e87 100644
--- a/car/app/app/src/main/java/androidx/car/app/serialization/ListDelegate.kt
+++ b/car/app/app/src/main/java/androidx/car/app/serialization/ListDelegate.kt
@@ -16,8 +16,6 @@
package androidx.car.app.serialization
import android.annotation.SuppressLint
-import androidx.annotation.RestrictTo
-import androidx.annotation.RestrictTo.Scope.LIBRARY
import androidx.car.app.OnDoneCallback
import androidx.car.app.annotations.ExperimentalCarApi
@@ -27,8 +25,7 @@
* <p> Long lists are stored on the client for performance reasons.
*/
@ExperimentalCarApi
-@RestrictTo(LIBRARY)
-interface ListDelegate<T> {
+interface ListDelegate<out T> {
/** The size of the underlying [List] */
val size: Int
diff --git a/car/app/app/src/main/java/androidx/car/app/serialization/ListDelegateImpl.kt b/car/app/app/src/main/java/androidx/car/app/serialization/ListDelegateImpl.kt
index 196397a..628be63 100644
--- a/car/app/app/src/main/java/androidx/car/app/serialization/ListDelegateImpl.kt
+++ b/car/app/app/src/main/java/androidx/car/app/serialization/ListDelegateImpl.kt
@@ -31,10 +31,20 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
class ListDelegateImpl<T> : ListDelegate<T> {
private var _size: Int = -1
+
+ /**
+ * The hash of the underlying list.
+ *
+ * This hash is used to determine whether two [ListDelegate]s contain the same items, without
+ * needing to load every item in the list.
+ */
+ private var listHashCode: Int = -1
+
private lateinit var mStub: IRemoteList
constructor(content: List<T>) {
_size = content.size
+ listHashCode = content.hashCode()
mStub = RemoteListStub<T>(content)
}
@@ -60,6 +70,11 @@
}
}
+ override fun equals(other: Any?) =
+ other is ListDelegateImpl<*> && other.listHashCode == listHashCode
+
+ override fun hashCode(): Int = listHashCode
+
private class RemoteListStub<T>(private val mContent: List<T>) : IRemoteList.Stub() {
@Throws(RemoteException::class)
override fun requestItemRange(startIndex: Int, endIndex: Int, callback: IOnDoneCallback) {
diff --git a/car/app/app/src/test/java/androidx/car/app/model/GridSectionTest.kt b/car/app/app/src/test/java/androidx/car/app/model/GridSectionTest.kt
index e6fe370..8d20b10 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/GridSectionTest.kt
+++ b/car/app/app/src/test/java/androidx/car/app/model/GridSectionTest.kt
@@ -46,26 +46,6 @@
}
@Test
- fun builderFromObject() {
- val section =
- GridSection.Builder()
- .setItemSize(GridSection.ITEM_SIZE_LARGE)
- .setItemImageShape(GridSection.ITEM_IMAGE_SHAPE_CIRCLE)
- .setItems(testItemList)
- .setTitle(testHeader)
- .setNoItemsMessage("Test no items message")
- .build()
-
- val result = GridSection.Builder(section).build()
-
- assertThat(result.itemSize).isEqualTo(GridSection.ITEM_SIZE_LARGE)
- assertThat(result.itemImageShape).isEqualTo(GridSection.ITEM_IMAGE_SHAPE_CIRCLE)
- assertThat(result.items).containsExactlyElementsIn(testItemList)
- assertThat(result.title).isEqualTo(testHeader)
- assertThat(result.noItemsMessage).isEqualTo(CarText.create("Test no items message"))
- }
-
- @Test
fun equals_returnsFalse_whenPassedNull() {
val section = GridSection.Builder().build()
diff --git a/car/app/app/src/test/java/androidx/car/app/model/RowSectionTest.kt b/car/app/app/src/test/java/androidx/car/app/model/RowSectionTest.kt
index dd4680a..66aad825 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/RowSectionTest.kt
+++ b/car/app/app/src/test/java/androidx/car/app/model/RowSectionTest.kt
@@ -102,25 +102,6 @@
}
@Test
- fun builderFromObject() {
- val section =
- RowSection.Builder()
- .setItems(testItemList)
- .setAsSelectionGroup(1)
- .setTitle(testHeader)
- .setNoItemsMessage(testNoItemsMessage)
- .build()
-
- val result = RowSection.Builder(section).build()
-
- assertThat(result.items).containsExactlyElementsIn(testItemList)
- assertThat(result.isSelectionGroup).isTrue()
- assertThat(result.initialSelectedIndex).isEqualTo(1)
- assertThat(result.title).isEqualTo(testHeader)
- assertThat(result.noItemsMessage).isEqualTo(testNoItemsMessage)
- }
-
- @Test
fun equals_returnsFalse_whenPassedNull() {
val section = RowSection.Builder().build()
diff --git a/car/app/app/src/test/java/androidx/car/app/model/SectionTest.kt b/car/app/app/src/test/java/androidx/car/app/model/SectionTest.kt
index bccf051..318c749 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/SectionTest.kt
+++ b/car/app/app/src/test/java/androidx/car/app/model/SectionTest.kt
@@ -18,6 +18,7 @@
import android.text.SpannableString
import android.text.Spanned
+import androidx.car.app.testing.TestDelegateInvoker.requestAllItemsForTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
@@ -27,7 +28,9 @@
@RunWith(RobolectricTestRunner::class)
class SectionTest {
/** An example item containing a uniquely identifying field. */
- private data class TestItem(val someUniquelyIdentifyingField: Int) : Item
+ private data class TestItem(val someUniquelyIdentifyingField: Int) : Item {
+ constructor() : this(-1)
+ }
/** An empty section implementation to test the base class. */
private class TestSection(builder: Builder) : Section<TestItem>(builder) {
@@ -42,7 +45,7 @@
val item = TestItem(1)
val section = TestSection.Builder().addItem(item).build()
- assertThat(section.items).containsExactly(item)
+ assertThat(section.itemsDelegate.requestAllItemsForTest()).containsExactly(item)
}
@Test
diff --git a/car/app/app/src/test/java/androidx/car/app/serialization/ListDelegateTest.kt b/car/app/app/src/test/java/androidx/car/app/serialization/ListDelegateTest.kt
index 7014c15..b5f8108 100644
--- a/car/app/app/src/test/java/androidx/car/app/serialization/ListDelegateTest.kt
+++ b/car/app/app/src/test/java/androidx/car/app/serialization/ListDelegateTest.kt
@@ -54,6 +54,22 @@
assertInvalidIndices(2, 1) // end before start
}
+ @Test
+ fun equalsAndHashCode_sameInstance_areEqual() =
+ ListDelegateImpl(testList).let { assertEqual(it, it) }
+
+ @Test
+ fun equalsAndHashCode_equivalentItems_areEqual() =
+ testList.let { assertEqual(ListDelegateImpl(it), ListDelegateImpl(it)) }
+
+ @Test
+ fun equalsAndHashCode_marshalledItem_areEqual() =
+ ListDelegateImpl(testList).let { assertEqual(it, marshallUnmarshall(it)) }
+
+ @Test
+ fun equalsAndHashCode_differentItems_areNotEqual() =
+ assertNotEqual(ListDelegateImpl((10..19).toList()), ListDelegateImpl((20..29).toList()))
+
private fun assertInvalidIndices(startIndex: Int, endIndex: Int) {
assertThrows(AssertionError::class.java) { requestItemRange(startIndex, endIndex) }
}
@@ -70,4 +86,20 @@
@Suppress("UNCHECKED_CAST") return resultCaptor.lastValue.get() as List<Int>
}
+
+ private fun <T : Any> assertEqual(a: T, b: T) {
+ assertThat(a).isEqualTo(b)
+ assertThat(b).isEqualTo(a)
+ assertThat(a.hashCode()).isEqualTo(b.hashCode())
+ }
+
+ private fun <T : Any> assertNotEqual(a: T, b: T) {
+ assertThat(a).isNotEqualTo(b)
+ assertThat(b).isNotEqualTo(a)
+ assertThat(a.hashCode()).isNotEqualTo(b.hashCode())
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <T : Any> marshallUnmarshall(obj: T): T =
+ Bundler.fromBundle(Bundler.toBundle(obj)) as T
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
index ef4fce6..55adc64 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.relocation.findBringIntoViewParent
import androidx.compose.foundation.relocation.scrollIntoView
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
@@ -39,6 +40,7 @@
import androidx.compose.ui.node.findNearestAncestor
import androidx.compose.ui.node.invalidateSemantics
import androidx.compose.ui.node.observeReads
+import androidx.compose.ui.node.requireLayoutCoordinates
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.focused
@@ -211,7 +213,15 @@
if (isFocused == wasFocused) return
if (isFocused) {
onFocus?.invoke()
- coroutineScope.launch { if (isAttached) scrollIntoView() }
+ val parent = findBringIntoViewParent()
+ if (parent != null) {
+ val layoutCoordinates = requireLayoutCoordinates()
+ coroutineScope.launch {
+ if (isAttached) {
+ parent.scrollIntoView(layoutCoordinates)
+ }
+ }
+ }
val pinnableContainer = retrievePinnableContainer()
pinnedHandle = pinnableContainer?.pin()
notifyObserverWhenAttached()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/ScrollIntoViewRequester.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/ScrollIntoViewRequester.kt
index 2fd1dc2..9a40c89 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/ScrollIntoViewRequester.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/ScrollIntoViewRequester.kt
@@ -21,6 +21,7 @@
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.toRect
+import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.requireLayoutCoordinates
import androidx.compose.ui.unit.toSize
@@ -46,7 +47,15 @@
if (!node.isAttached) return
val layoutCoordinates = requireLayoutCoordinates()
val parent = findBringIntoViewParent() ?: return
- parent.bringChildIntoView(layoutCoordinates) {
+ parent.scrollIntoView(layoutCoordinates, rect)
+}
+
+internal suspend fun BringIntoViewParent.scrollIntoView(
+ layoutCoordinates: LayoutCoordinates,
+ rect: Rect? = null
+) {
+ if (!layoutCoordinates.isAttached) return
+ bringChildIntoView(layoutCoordinates) {
// If the rect is not specified, use a rectangle representing the entire composable.
// If the coordinates are detached when this call is made, we don't bother even
// submitting the request, but if the coordinates become detached while the request
diff --git a/compose/integration-tests/macrobenchmark-target/build.gradle b/compose/integration-tests/macrobenchmark-target/build.gradle
index 8e43d97..5946d76 100644
--- a/compose/integration-tests/macrobenchmark-target/build.gradle
+++ b/compose/integration-tests/macrobenchmark-target/build.gradle
@@ -29,6 +29,7 @@
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation(libs.kotlinStdlib)
+ implementation(libs.material)
implementation(project(":activity:activity-compose"))
implementation("androidx.appcompat:appcompat:1.4.1")
implementation("androidx.cardview:cardview:1.0.0")
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index 0eadc27..d249e08 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -20,12 +20,11 @@
<uses-permission android:name="android.permission.INTERNET"/>
<application
- android:label="Jetpack Compose Macrobenchmark Target"
android:allowBackup="false"
- android:supportsRtl="true"
android:icon="@mipmap/ic_launcher"
+ android:label="Jetpack Compose Macrobenchmark Target"
+ android:supportsRtl="true"
tools:ignore="GoogleAppIndexingWarning">
-
<!-- Profileable to enable macrobenchmark profiling -->
<profileable android:shell="true"/>
@@ -37,8 +36,8 @@
-->
<activity
android:name=".TrivialStartupActivity"
- android:label="C Trivial"
- android:exported="true">
+ android:exported="true"
+ android:label="C Trivial">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -50,8 +49,8 @@
</activity>
<activity
android:name=".StaticScrollingContentWithChromeInitialCompositionActivity"
- android:label="C StaticScrollingWithChrome Init"
- android:exported="true">
+ android:exported="true"
+ android:label="C StaticScrollingWithChrome Init">
<intent-filter>
<action android:name="androidx.compose.integration.macrobenchmark.target.STATIC_SCROLLING_CONTENT_WITH_CHROME_INITIAL_COMPOSITION_ACTIVITY" />
<category android:name="android.intent.category.DEFAULT" />
@@ -63,8 +62,8 @@
</activity>
<activity
android:name=".TrivialStartupTracingActivity"
- android:label="C TrivialTracing"
- android:exported="true">
+ android:exported="true"
+ android:label="C TrivialTracing">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -92,8 +91,8 @@
</activity>
<activity
android:name=".LazyColumnActivity"
- android:label="C LazyColumn"
- android:exported="true">
+ android:exported="true"
+ android:label="C LazyColumn">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -105,8 +104,8 @@
</activity>
<activity
android:name=".FrameExperimentActivity"
- android:label="FrameExp"
- android:exported="true">
+ android:exported="true"
+ android:label="FrameExp">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -176,7 +175,6 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
-
<activity
android:name=".ViewPagerActivity"
android:exported="true"
@@ -186,7 +184,6 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
-
<activity
android:name=".RecyclerViewAsCarouselActivity"
android:exported="true"
@@ -196,7 +193,6 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
-
<activity
android:name=".PagerAsCarouselActivity"
android:exported="true"
@@ -206,7 +202,6 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
-
<activity
android:name=".PagerActivity"
android:exported="true"
@@ -216,7 +211,6 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
-
<activity
android:name=".TrivialTracingActivity"
android:exported="true">
@@ -225,7 +219,8 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
- <activity android:name=".AndroidViewListActivity"
+ <activity
+ android:name=".AndroidViewListActivity"
android:exported="true"
android:theme="@style/Theme.AppCompat">
<intent-filter>
@@ -233,7 +228,8 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
- <activity android:name=".RecyclerViewListActivity"
+ <activity
+ android:name=".RecyclerViewListActivity"
android:exported="true"
android:theme="@style/Theme.AppCompat">
<intent-filter>
@@ -241,11 +237,10 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
-
- <activity
+ <activity
android:name=".VectorsListActivity"
- android:label="Compose vectors list"
- android:exported="true">
+ android:exported="true"
+ android:label="Compose vectors list">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -255,11 +250,10 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
-
<activity
android:name=".CrossfadeActivity"
- android:label="Compose Crossfade Benchmark"
- android:exported="true">
+ android:exported="true"
+ android:label="Compose Crossfade Benchmark">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -268,9 +262,9 @@
<action android:name="androidx.compose.integration.macrobenchmark.target.CROSSFADE_ACTIVITY" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
- </activity>
-
- <activity android:name=".PagerOfLazyGridActivity"
+ </activity>
+ <activity
+ android:name=".PagerOfLazyGridActivity"
android:exported="true"
android:theme="@style/Theme.AppCompat">
<intent-filter>
@@ -278,5 +272,20 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+ <activity
+ android:name=".FormFillingActivity"
+ android:exported="true"
+ android:label="Compose Form Filling Benchmark"
+ android:launchMode="singleInstance"
+ android:theme="@style/Theme.AppCompat.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="androidx.compose.integration.macrobenchmark.target.FORM_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/FormFillingActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/FormFillingActivity.kt
new file mode 100644
index 0000000..2958dfe
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/FormFillingActivity.kt
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2024 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.integration.macrobenchmark.target
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.EditText
+import android.widget.LinearLayout
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.fastRoundToInt
+import androidx.compose.ui.util.trace
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.Adapter
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+
+class FormFillingActivity : ComponentActivity() {
+ private lateinit var lazyListState: LazyListState
+ private lateinit var formView: FormView
+ private lateinit var type: String
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val rowHeightDp: Dp
+ val fontSize: TextUnit
+ when (intent.getIntExtra(MODE, 0)) {
+ FRAME_MEASUREMENT_MODE -> {
+ // Larger number of rows to stress the system while measuring frame info.
+ rowHeightDp = 30.dp
+ fontSize = 5.sp
+ }
+ CREATE_ANI_MODE -> {
+ // Smaller number of rows so that we have no dropped frames.
+ rowHeightDp = 100.dp
+ fontSize = 10.sp
+ }
+ else -> error("Invalid Mode")
+ }
+
+ type = checkNotNull(intent.getStringExtra(TYPE)) { "No type specified." }
+ when (type) {
+ COMPOSE ->
+ setContent {
+ lazyListState = rememberLazyListState()
+ FormComposable(lazyListState, rowHeightDp, fontSize)
+ }
+ VIEW -> {
+ val rowHeightPx = rowHeightDp.value * resources.displayMetrics.densityDpi / 160f
+ formView = FormView(this, rowHeightPx, fontSize)
+ setContentView(formView)
+ }
+ else -> error("Unknown Type")
+ }
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ when (type) {
+ COMPOSE -> lazyListState.requestScrollToItem(lazyListState.firstVisibleItemIndex + 100)
+ VIEW -> formView.scrollToPosition(formView.lastVisibleItemIndex + 100)
+ else -> error("Unknown Type")
+ }
+ super.onNewIntent(intent)
+ }
+
+ @Composable
+ private fun FormComposable(lazyListState: LazyListState, rowHeight: Dp, fontSize: TextUnit) {
+ val textStyle = LocalTextStyle.current.copy(fontSize = fontSize)
+ LazyColumn(state = lazyListState) {
+ items(data.size) { index ->
+ val person = data[index]
+ Row(
+ modifier =
+ Modifier.height(rowHeight).semantics {
+ customActions =
+ listOf(CustomAccessibilityAction("customAction") { false })
+ }
+ ) {
+ BasicTextField(
+ value = person.title,
+ onValueChange = { person.title = it },
+ textStyle = textStyle
+ )
+ BasicTextField(
+ value = person.firstName,
+ onValueChange = { person.firstName = it },
+ textStyle = textStyle
+ )
+ BasicTextField(
+ value = person.middleName,
+ onValueChange = { person.middleName = it },
+ textStyle = textStyle
+ )
+ BasicTextField(
+ value = person.lastName,
+ onValueChange = { person.lastName = it },
+ textStyle = textStyle
+ )
+ BasicTextField(
+ value = person.age.toString(),
+ onValueChange = { person.age = it.toInt() },
+ textStyle = textStyle
+ )
+ }
+ }
+ }
+ }
+
+ private class FormView(context: Context, rowHeight: Float, fontSize: TextUnit) :
+ RecyclerView(context) {
+ private val linearLayoutManager: LinearLayoutManager
+
+ init {
+ setHasFixedSize(true)
+ linearLayoutManager = LinearLayoutManager(context, VERTICAL, false)
+ layoutManager = linearLayoutManager
+ adapter = DemoAdapter(data, rowHeight, fontSize)
+ }
+
+ val lastVisibleItemIndex: Int
+ get() = linearLayoutManager.findLastVisibleItemPosition()
+
+ override fun createAccessibilityNodeInfo(): AccessibilityNodeInfo {
+ return trace(CREATE_ANI_TRACE) { super.createAccessibilityNodeInfo() }
+ }
+
+ override fun sendAccessibilityEvent(eventType: Int) {
+ return trace(ACCESSIBILITY_EVENT_TRACE) { super.sendAccessibilityEvent(eventType) }
+ }
+ }
+
+ private class RowView(context: Context, content: (RowView) -> Unit) : LinearLayout(context) {
+ init {
+ gravity = Gravity.CENTER_VERTICAL
+ content(this)
+ }
+
+ override fun createAccessibilityNodeInfo(): AccessibilityNodeInfo {
+ return trace(CREATE_ANI_TRACE) { super.createAccessibilityNodeInfo() }
+ }
+
+ override fun sendAccessibilityEvent(eventType: Int) {
+ return trace(ACCESSIBILITY_EVENT_TRACE) { super.sendAccessibilityEvent(eventType) }
+ }
+ }
+
+ @SuppressLint("AppCompatCustomView")
+ private class EditTextView(context: Context, fontSize: Float) : EditText(context) {
+ init {
+ textSize = fontSize
+ gravity = Gravity.CENTER_VERTICAL
+ }
+
+ fun replaceText(newText: String) {
+ text.replace(0, length(), newText, 0, newText.length)
+ }
+
+ override fun createAccessibilityNodeInfo(): AccessibilityNodeInfo {
+ return trace(CREATE_ANI_TRACE) { super.createAccessibilityNodeInfo() }
+ }
+
+ override fun sendAccessibilityEvent(eventType: Int) {
+ return trace(ACCESSIBILITY_EVENT_TRACE) { super.sendAccessibilityEvent(eventType) }
+ }
+ }
+
+ private class DemoAdapter(
+ val data: List<FormData>,
+ val rowHeightPx: Float,
+ textSize: TextUnit
+ ) : Adapter<DemoAdapter.DemoViewHolder>() {
+
+ private class DemoViewHolder(
+ val title: EditTextView,
+ val firstName: EditTextView,
+ val middleName: EditTextView,
+ val lastName: EditTextView,
+ val age: EditTextView,
+ itemRoot: View
+ ) : ViewHolder(itemRoot)
+
+ val textSize = textSize.value
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DemoViewHolder {
+ val title = EditTextView(parent.context, textSize)
+ val firstName = EditTextView(parent.context, textSize)
+ val middleName = EditTextView(parent.context, textSize)
+ val lastName = EditTextView(parent.context, textSize)
+ val age = EditTextView(parent.context, textSize)
+
+ return DemoViewHolder(
+ title,
+ firstName,
+ middleName,
+ lastName,
+ age,
+ RowView(parent.context) {
+ it.minimumHeight = rowHeightPx.fastRoundToInt()
+ it.addView(title)
+ it.addView(firstName)
+ it.addView(middleName)
+ it.addView(lastName)
+ it.addView(age)
+ }
+ )
+ }
+
+ override fun onBindViewHolder(holder: DemoViewHolder, position: Int) {
+ val formData = data.elementAt(position)
+ holder.title.replaceText(formData.title)
+ holder.firstName.replaceText(formData.firstName)
+ holder.middleName.replaceText(formData.middleName)
+ holder.lastName.replaceText(formData.lastName)
+ holder.age.replaceText(formData.age.toString())
+ }
+
+ override fun getItemCount(): Int = data.size
+ }
+
+ private data class FormData(
+ var title: String = "",
+ var firstName: String = "",
+ var middleName: String = "",
+ var lastName: String = "",
+ var age: Int = 0,
+ )
+
+ private companion object {
+ private const val TYPE = "TYPE"
+ private const val COMPOSE = "Compose"
+ private const val VIEW = "View"
+ private const val MODE = "MODE"
+ private const val CREATE_ANI_MODE = 1
+ private const val FRAME_MEASUREMENT_MODE = 2
+ private const val CREATE_ANI_TRACE = "createAccessibilityNodeInfo"
+ private const val ACCESSIBILITY_EVENT_TRACE = "sendAccessibilityEvent"
+ private val data by lazy {
+ List(200000) {
+ FormData(
+ title = "Mr",
+ firstName = "John $it",
+ middleName = "Ace $it",
+ lastName = "Doe $it",
+ age = it
+ )
+ }
+ }
+ }
+}
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/FormFillingBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/FormFillingBenchmark.kt
new file mode 100644
index 0000000..9b05e73
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/FormFillingBenchmark.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2024 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.integration.macrobenchmark
+
+import android.app.Instrumentation
+import android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES
+import android.content.Intent
+import android.os.Build.VERSION_CODES.N
+import android.provider.Settings.Secure
+import androidx.benchmark.macro.ExperimentalMetricApi
+import androidx.benchmark.macro.FrameTimingMetric
+import androidx.benchmark.macro.TraceSectionMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.Configurator
+import androidx.test.uiautomator.UiDevice
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@SdkSuppress(minSdkVersion = N)
+@LargeTest
+@RunWith(Parameterized::class)
+class FormFillingBenchmark(private var talkbackEnabled: Boolean, private val type: String) {
+
+ @get:Rule val benchmarkRule = MacrobenchmarkRule()
+ private lateinit var instrumentation: Instrumentation
+ private var previousTalkbackSettings: String? = null
+ private lateinit var device: UiDevice
+
+ @Test
+ fun createAccessibilityNodeInfo() {
+ if (!talkbackEnabled) return
+ benchmarkRule.measureRepeated(
+ packageName = PACKAGE,
+ metrics =
+ @OptIn(ExperimentalMetricApi::class)
+ listOf(
+ TraceSectionMetric(
+ sectionName = CREATE_ANI_TRACE,
+ mode = TraceSectionMetric.Mode.Sum
+ ),
+ TraceSectionMetric(
+ sectionName = ACCESSIBILITY_EVENT_TRACE,
+ mode = TraceSectionMetric.Mode.Sum
+ )
+ ),
+ iterations = 10,
+ setupBlock = {
+ if (iteration == 0) {
+ startActivityAndWait(
+ Intent()
+ .setAction("$PACKAGE.$ACTIVITY")
+ .putExtra(TYPE, type)
+ .putExtra(MODE, CREATE_ANI_MODE)
+ )
+ device.waitForIdle()
+
+ // Run one iteration to allow the scroll position to stabilize, and to remove
+ // the effect of the initial frame which draws the accessibility focus box.
+ performScrollAndWait(millis = 10_000)
+ }
+ },
+ measureBlock = {
+
+ // Scroll and pause to allow all frames to complete, for the accessibility events
+ // to be sent, for talkback to assign focus, and finally for talkback to trigger
+ // createAccessibilityNodeInfo calls which is the thing we want to measure.
+ performScrollAndWait(millis = 10_000)
+ }
+ )
+ }
+
+ @Test
+ fun frameInfo() {
+ benchmarkRule.measureRepeated(
+ packageName = PACKAGE,
+ metrics = listOf(FrameTimingMetric()),
+ iterations = 10,
+ setupBlock = {
+ if (iteration == 0) {
+ startActivityAndWait(
+ Intent()
+ .setAction("$PACKAGE.$ACTIVITY")
+ .putExtra(TYPE, type)
+ .putExtra(MODE, FRAME_MEASUREMENT_MODE)
+ )
+ Thread.sleep(2_000)
+ device.waitForIdle()
+
+ // Run one iteration to allow the scroll position to stabilize, and to remove
+ // the effect of the initial frame which draws the accessibility focus box.
+ performScrollAndWait(millis = 20)
+ }
+ },
+ measureBlock = {
+ // Instead of using an animation to scroll (Where the number of frames triggered
+ // is not deterministic, we attempt to scroll 100 times with an aim to scroll once
+ // every frame deadline of 20ms.
+ repeat(100) { performScrollAndWait(millis = 20) }
+ Thread.sleep(10_000)
+ }
+ )
+ }
+
+ @Before
+ fun setUp() {
+ Configurator.getInstance().uiAutomationFlags = FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES
+ instrumentation = InstrumentationRegistry.getInstrumentation()
+ device = UiDevice.getInstance(instrumentation)
+ if (talkbackEnabled) {
+ previousTalkbackSettings = instrumentation.enableTalkback()
+ // Wait for talkback to turn on.
+ Thread.sleep(2_000)
+ }
+ }
+
+ @After
+ fun tearDown() {
+ if (talkbackEnabled) {
+ instrumentation.disableTalkback(previousTalkbackSettings)
+ // Wait for talkback to turn off.
+ Thread.sleep(2_000)
+ }
+ }
+
+ private fun performScrollAndWait(millis: Long) {
+ // We don't use UI Automator to scroll because UI Automator itself is an accessibility
+ // service, and this affects the benchmark. Instead we send an event to the activity that
+ // requests it to scroll.
+ instrumentation.context.startActivity(
+ Intent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setAction("$PACKAGE.$ACTIVITY")
+ )
+
+ // Pause to allow all frames to complete, for the accessibility events to be sent,
+ // for talkback to assign focus, and finally for talkback to trigger
+ // createAccessibilityNodeInfo calls which is the thing we want to measure.
+ Thread.sleep(millis)
+ }
+
+ companion object {
+ private const val PACKAGE = "androidx.compose.integration.macrobenchmark.target"
+ private const val ACTIVITY = "FORM_ACTIVITY"
+ private const val TYPE = "TYPE"
+ private const val COMPOSE = "Compose"
+ private const val VIEW = "View"
+ const val MODE = "MODE"
+ const val CREATE_ANI_MODE = 1
+ const val FRAME_MEASUREMENT_MODE = 2
+ const val CREATE_ANI_TRACE = "createAccessibilityNodeInfo"
+ const val ACCESSIBILITY_EVENT_TRACE = "sendAccessibilityEvent"
+
+ // Manually set up LastPass on the device and use these parameters when running locally.
+ // @Parameterized.Parameters(name = "LastPassEnabled=true, type={1}")
+ // @JvmStatic
+ // fun parameters() = mutableListOf<Array<Any>>().also {
+ // for (type in arrayOf(COMPOSE, VIEW)) {
+ // it.add(arrayOf(false, type))
+ // }
+ // }
+
+ @Parameterized.Parameters(name = "TalkbackEnabled={0}, type={1}")
+ @JvmStatic
+ fun parameters() =
+ mutableListOf<Array<Any>>().also {
+ for (talkbackEnabled in arrayOf(false, true)) {
+ for (type in arrayOf(COMPOSE, VIEW)) {
+ it.add(arrayOf(talkbackEnabled, type))
+ }
+ }
+ }
+ }
+}
+
+private fun Instrumentation.enableTalkback(): String? {
+ val talkback =
+ "com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService"
+ val previousTalkbackSettings =
+ Secure.getString(context.contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES)
+ UiDevice.getInstance(this)
+ .executeShellCommand("settings put secure enabled_accessibility_services $talkback")
+ return previousTalkbackSettings
+}
+
+private fun Instrumentation.disableTalkback(previousTalkbackSettings: String? = null): String {
+ return UiDevice.getInstance(this)
+ .executeShellCommand(
+ if (previousTalkbackSettings == null || previousTalkbackSettings == "") {
+ "settings delete secure enabled_accessibility_services"
+ } else {
+ "settings put secure enabled_accessibility_services $previousTalkbackSettings"
+ }
+ )
+}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 9e67cb0..a4960cc 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1402,7 +1402,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long focusedContainerColor, optional long unfocusedContainerColor, optional long disabledContainerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors? selectionColors, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
method public androidx.compose.foundation.layout.PaddingValues contentPadding(optional float start, optional float top, optional float end, optional float bottom);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean alwaysMinimizeLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
method public float getFocusedBorderThickness();
method public float getMinHeight();
method public float getMinWidth();
@@ -1417,7 +1417,7 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional boolean alwaysMinimizeLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
@@ -2199,7 +2199,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long focusedContainerColor, optional long unfocusedContainerColor, optional long disabledContainerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors? selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
method public androidx.compose.foundation.layout.PaddingValues contentPaddingWithLabel(optional float start, optional float end, optional float top, optional float bottom);
method public androidx.compose.foundation.layout.PaddingValues contentPaddingWithoutLabel(optional float start, optional float top, optional float end, optional float bottom);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean alwaysMinimizeLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledShape();
method @Deprecated public float getFocusedBorderThickness();
method public float getFocusedIndicatorThickness();
@@ -2226,7 +2226,7 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+ method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional boolean alwaysMinimizeLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
method @androidx.compose.runtime.Composable public static void TextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 9e67cb0..a4960cc 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1402,7 +1402,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors();
method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long focusedContainerColor, optional long unfocusedContainerColor, optional long disabledContainerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors? selectionColors, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
method public androidx.compose.foundation.layout.PaddingValues contentPadding(optional float start, optional float top, optional float end, optional float bottom);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean alwaysMinimizeLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
method public float getFocusedBorderThickness();
method public float getMinHeight();
method public float getMinWidth();
@@ -1417,7 +1417,7 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional boolean alwaysMinimizeLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
@@ -2199,7 +2199,7 @@
method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long focusedContainerColor, optional long unfocusedContainerColor, optional long disabledContainerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors? selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
method public androidx.compose.foundation.layout.PaddingValues contentPaddingWithLabel(optional float start, optional float end, optional float top, optional float bottom);
method public androidx.compose.foundation.layout.PaddingValues contentPaddingWithoutLabel(optional float start, optional float top, optional float end, optional float bottom);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.foundation.text.input.TextFieldDecorator decorator(androidx.compose.foundation.text.input.TextFieldState state, boolean enabled, androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean alwaysMinimizeLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFilledShape();
method @Deprecated public float getFocusedBorderThickness();
method public float getFocusedIndicatorThickness();
@@ -2226,7 +2226,7 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+ method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional boolean alwaysMinimizeLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.foundation.text.input.OutputTransformation? outputTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.input.KeyboardActionHandler? onKeyboardAction, optional androidx.compose.foundation.text.input.TextFieldLineLimits lineLimits, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
method @androidx.compose.runtime.Composable public static void TextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt
index 61abf46..6e9b7fd 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TextFieldSamples.kt
@@ -20,6 +20,7 @@
import androidx.annotation.Sampled
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -39,6 +40,7 @@
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
+import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -142,26 +144,44 @@
@Sampled
@Composable
fun TextFieldWithPlaceholder() {
- TextField(
- state = rememberTextFieldState(),
- lineLimits = TextFieldLineLimits.SingleLine,
- label = { Text("Email") },
- placeholder = { Text("[email protected]") }
- )
+ var alwaysMinimizeLabel by remember { mutableStateOf(false) }
+ Column {
+ Row {
+ Checkbox(checked = alwaysMinimizeLabel, onCheckedChange = { alwaysMinimizeLabel = it })
+ Text("Show placeholder even when unfocused")
+ }
+ Spacer(Modifier.height(16.dp))
+ TextField(
+ state = rememberTextFieldState(),
+ lineLimits = TextFieldLineLimits.SingleLine,
+ label = { Text("Email") },
+ alwaysMinimizeLabel = alwaysMinimizeLabel,
+ placeholder = { Text("[email protected]") }
+ )
+ }
}
@Preview
@Sampled
@Composable
fun TextFieldWithPrefixAndSuffix() {
- TextField(
- state = rememberTextFieldState(),
- lineLimits = TextFieldLineLimits.SingleLine,
- label = { Text("Label") },
- prefix = { Text("www.") },
- suffix = { Text(".com") },
- placeholder = { Text("google") },
- )
+ var alwaysMinimizeLabel by remember { mutableStateOf(false) }
+ Column {
+ Row {
+ Checkbox(checked = alwaysMinimizeLabel, onCheckedChange = { alwaysMinimizeLabel = it })
+ Text("Show placeholder even when unfocused")
+ }
+ Spacer(Modifier.height(16.dp))
+ TextField(
+ state = rememberTextFieldState(),
+ lineLimits = TextFieldLineLimits.SingleLine,
+ label = { Text("Label") },
+ alwaysMinimizeLabel = alwaysMinimizeLabel,
+ prefix = { Text("www.") },
+ suffix = { Text(".com") },
+ placeholder = { Text("google") },
+ )
+ }
}
@Preview
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/OutlinedTextFieldScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/OutlinedTextFieldScreenshotTest.kt
index 6f20f08..32e7efe 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/OutlinedTextFieldScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/OutlinedTextFieldScreenshotTest.kt
@@ -593,6 +593,35 @@
}
@Test
+ fun outlinedTextField_alwaysMinimizeLabel_noPlaceholder() {
+ rule.setMaterialContent(lightColorScheme()) {
+ OutlinedTextField(
+ state = rememberTextFieldState(),
+ modifier = Modifier.testTag(TextFieldTag),
+ label = { Text("Label") },
+ alwaysMinimizeLabel = true,
+ )
+ }
+
+ assertAgainstGolden("outlinedTextField_alwaysMinimizeLabel_noPlaceholder")
+ }
+
+ @Test
+ fun outlinedTextField_alwaysMinimizeLabel_withPlaceholder() {
+ rule.setMaterialContent(lightColorScheme()) {
+ OutlinedTextField(
+ state = rememberTextFieldState(),
+ modifier = Modifier.testTag(TextFieldTag),
+ label = { Text("Label") },
+ alwaysMinimizeLabel = true,
+ placeholder = { Text("Placeholder") },
+ )
+ }
+
+ assertAgainstGolden("outlinedTextField_alwaysMinimizeLabel_withPlaceholder")
+ }
+
+ @Test
fun outlinedTextField_prefixSuffix_withLabelAndInput() {
rule.setMaterialContent(lightColorScheme()) {
OutlinedTextField(
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
index b5d1385..db825fd 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
@@ -77,6 +77,7 @@
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.click
@@ -897,6 +898,53 @@
}
@Test
+ fun testOutlinedTextField_prefixAndSuffixAndPlaceholder_areNotDisplayed_withLabel_ifLabelCanExpand() {
+ val labelText = "Label"
+ val prefixText = "Prefix"
+ val suffixText = "Suffix"
+ val placeholderText = "Placeholder"
+ rule.setMaterialContent(lightColorScheme()) {
+ OutlinedTextField(
+ state = rememberTextFieldState(),
+ label = { Text(labelText) },
+ prefix = { Text(prefixText) },
+ suffix = { Text(suffixText) },
+ placeholder = { Text(placeholderText) },
+ alwaysMinimizeLabel = false,
+ )
+ }
+
+ rule.onNodeWithText(labelText).assertIsDisplayed()
+
+ rule.onNodeWithText(prefixText).assertIsNotDisplayed()
+ rule.onNodeWithText(suffixText).assertIsNotDisplayed()
+ rule.onNodeWithText(placeholderText).assertIsNotDisplayed()
+ }
+
+ @Test
+ fun testOutlinedTextField_prefixAndSuffixAndPlaceholder_areDisplayed_withLabel_ifLabelCannotExpand() {
+ val labelText = "Label"
+ val prefixText = "Prefix"
+ val suffixText = "Suffix"
+ val placeholderText = "Placeholder"
+ rule.setMaterialContent(lightColorScheme()) {
+ OutlinedTextField(
+ state = rememberTextFieldState(),
+ label = { Text(labelText) },
+ prefix = { Text(prefixText) },
+ suffix = { Text(suffixText) },
+ placeholder = { Text(placeholderText) },
+ alwaysMinimizeLabel = true,
+ )
+ }
+
+ rule.onNodeWithText(labelText).assertIsDisplayed()
+ rule.onNodeWithText(prefixText).assertIsDisplayed()
+ rule.onNodeWithText(suffixText).assertIsDisplayed()
+ rule.onNodeWithText(placeholderText).assertIsDisplayed()
+ }
+
+ @Test
fun testOutlinedTextField_prefixAndSuffixPosition_withLabel() {
val textFieldWidth = 300.dp
val prefixPosition = Ref<Offset>()
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldScreenshotTest.kt
index 5e44ee9..f38d737 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldScreenshotTest.kt
@@ -559,6 +559,35 @@
}
@Test
+ fun textField_alwaysMinimizeLabel_noPlaceholder() {
+ rule.setMaterialContent(lightColorScheme()) {
+ TextField(
+ state = rememberTextFieldState(),
+ modifier = Modifier.testTag(TextFieldTag),
+ label = { Text("Label") },
+ alwaysMinimizeLabel = true,
+ )
+ }
+
+ assertAgainstGolden("textField_alwaysMinimizeLabel_noPlaceholder")
+ }
+
+ @Test
+ fun textField_alwaysMinimizeLabel_withPlaceholder() {
+ rule.setMaterialContent(lightColorScheme()) {
+ TextField(
+ state = rememberTextFieldState(),
+ modifier = Modifier.testTag(TextFieldTag),
+ label = { Text("Label") },
+ alwaysMinimizeLabel = true,
+ placeholder = { Text("Placeholder") },
+ )
+ }
+
+ assertAgainstGolden("textField_alwaysMinimizeLabel_withPlaceholder")
+ }
+
+ @Test
fun textField_prefixSuffix_withLabelAndInput() {
rule.setMaterialContent(lightColorScheme()) {
TextField(
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldTest.kt
index a5069cf..4f290a9 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TextFieldTest.kt
@@ -89,6 +89,7 @@
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.click
@@ -917,6 +918,53 @@
}
@Test
+ fun testTextField_prefixAndSuffixAndPlaceholder_areNotDisplayed_withLabel_ifLabelCanExpand() {
+ val labelText = "Label"
+ val prefixText = "Prefix"
+ val suffixText = "Suffix"
+ val placeholderText = "Placeholder"
+ rule.setMaterialContent(lightColorScheme()) {
+ TextField(
+ state = rememberTextFieldState(),
+ label = { Text(labelText) },
+ prefix = { Text(prefixText) },
+ suffix = { Text(suffixText) },
+ placeholder = { Text(placeholderText) },
+ alwaysMinimizeLabel = false,
+ )
+ }
+
+ rule.onNodeWithText(labelText).assertIsDisplayed()
+
+ rule.onNodeWithText(prefixText).assertIsNotDisplayed()
+ rule.onNodeWithText(suffixText).assertIsNotDisplayed()
+ rule.onNodeWithText(placeholderText).assertIsNotDisplayed()
+ }
+
+ @Test
+ fun testTextField_prefixAndSuffixAndPlaceholder_areDisplayed_withLabel_ifLabelCannotExpand() {
+ val labelText = "Label"
+ val prefixText = "Prefix"
+ val suffixText = "Suffix"
+ val placeholderText = "Placeholder"
+ rule.setMaterialContent(lightColorScheme()) {
+ TextField(
+ state = rememberTextFieldState(),
+ label = { Text(labelText) },
+ prefix = { Text(prefixText) },
+ suffix = { Text(suffixText) },
+ placeholder = { Text(placeholderText) },
+ alwaysMinimizeLabel = true,
+ )
+ }
+
+ rule.onNodeWithText(labelText).assertIsDisplayed()
+ rule.onNodeWithText(prefixText).assertIsDisplayed()
+ rule.onNodeWithText(suffixText).assertIsDisplayed()
+ rule.onNodeWithText(placeholderText).assertIsDisplayed()
+ }
+
+ @Test
fun testTextField_prefixAndSuffixPosition_withLabel() {
val prefixPosition = Ref<Offset>()
val prefixSize = MinTextLineHeight
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
index 4d4d865..a628d71 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
@@ -21,6 +21,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.internal.ProvideContentColorTextStyle
import androidx.compose.material3.internal.heightOrZero
+import androidx.compose.material3.internal.subtractConstraintSafely
import androidx.compose.material3.internal.widthOrZero
import androidx.compose.material3.tokens.ListTokens
import androidx.compose.material3.tokens.TypographyKeyTokens
@@ -769,10 +770,3 @@
ListItemType.ThreeLine -> ListItemThreeLineVerticalPadding
else -> ListItemVerticalPadding
}
-
-private fun Int.subtractConstraintSafely(n: Int): Int {
- if (this == Constraints.Infinity) {
- return this
- }
- return this - n
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
index d2affed..fc461d4 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
@@ -56,11 +56,11 @@
import androidx.compose.material3.internal.SupportingId
import androidx.compose.material3.internal.TextFieldId
import androidx.compose.material3.internal.TrailingId
-import androidx.compose.material3.internal.ZeroConstraints
import androidx.compose.material3.internal.defaultErrorSemantics
import androidx.compose.material3.internal.getString
import androidx.compose.material3.internal.heightOrZero
import androidx.compose.material3.internal.layoutId
+import androidx.compose.material3.internal.subtractConstraintSafely
import androidx.compose.material3.internal.widthOrZero
import androidx.compose.material3.tokens.TypeScaleTokens
import androidx.compose.runtime.Composable
@@ -135,9 +135,12 @@
* be modified. However, a user can focus it and copy text from it. Read-only text fields are
* usually used to display pre-filled forms that a user cannot edit.
* @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle].
+ * @param alwaysMinimizeLabel whether to always minimize the label of this text field. Defaults to
+ * `false`, so the label will expand to occupy the input area when the text field is unfocused and
+ * empty. When `true`, this allows displaying the [placeholder], [prefix], and [suffix] alongside
+ * the [label] when the text field is unfocused and empty.
* @param label the optional label to be displayed with this text field. The default text style uses
- * [Typography.bodySmall] when the text field is in focus and [Typography.bodyLarge] when the text
- * field is not in focus.
+ * [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
* @param placeholder the optional placeholder to be displayed when the input text is empty. The
* default text style uses [Typography.bodyLarge].
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
@@ -198,6 +201,7 @@
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
+ alwaysMinimizeLabel: Boolean = false,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
@@ -270,6 +274,7 @@
lineLimits = lineLimits,
outputTransformation = outputTransformation,
interactionSource = interactionSource,
+ alwaysMinimizeLabel = alwaysMinimizeLabel,
label = label,
placeholder = placeholder,
leadingIcon = leadingIcon,
@@ -320,9 +325,8 @@
* be modified. However, a user can focus it and copy text from it. Read-only text fields are
* usually used to display pre-filled forms that a user cannot edit.
* @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle].
- * @param label the optional label to be displayed inside the text field container. The default text
- * style for internal [Text] is [Typography.bodySmall] when the text field is in focus and
- * [Typography.bodyLarge] when the text field is not in focus
+ * @param label the optional label to be displayed with this text field. The default text style uses
+ * [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the input text is empty. The default text style for internal [Text] is [Typography.bodyLarge]
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
@@ -491,9 +495,8 @@
* be modified. However, a user can focus it and copy text from it. Read-only text fields are
* usually used to display pre-filled forms that a user cannot edit.
* @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle].
- * @param label the optional label to be displayed inside the text field container. The default text
- * style for internal [Text] is [Typography.bodySmall] when the text field is in focus and
- * [Typography.bodyLarge] when the text field is not in focus
+ * @param label the optional label to be displayed with this text field. The default text style uses
+ * [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the input text is empty. The default text style for internal [Text] is [Typography.bodyLarge]
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
@@ -651,18 +654,18 @@
prefix: @Composable (() -> Unit)?,
suffix: @Composable (() -> Unit)?,
singleLine: Boolean,
- animationProgress: Float,
+ labelProgress: Float,
onLabelMeasured: (Size) -> Unit,
container: @Composable () -> Unit,
supporting: @Composable (() -> Unit)?,
paddingValues: PaddingValues
) {
val measurePolicy =
- remember(onLabelMeasured, singleLine, animationProgress, paddingValues) {
+ remember(onLabelMeasured, singleLine, labelProgress, paddingValues) {
OutlinedTextFieldMeasurePolicy(
onLabelMeasured,
singleLine,
- animationProgress,
+ labelProgress,
paddingValues
)
}
@@ -748,12 +751,7 @@
if (label != null) {
Box(
Modifier.heightIn(
- min =
- lerp(
- MinTextLineHeight,
- MinFocusedLabelLineHeight,
- animationProgress
- )
+ min = lerp(MinTextLineHeight, MinFocusedLabelLineHeight, labelProgress)
)
.wrapContentHeight()
.layoutId(LabelId)
@@ -780,7 +778,7 @@
private class OutlinedTextFieldMeasurePolicy(
private val onLabelMeasured: (Size) -> Unit,
private val singleLine: Boolean,
- private val animationProgress: Float,
+ private val labelProgress: Float,
private val paddingValues: PaddingValues
) : MeasurePolicy {
override fun MeasureScope.measure(
@@ -796,32 +794,32 @@
// measure leading icon
val leadingPlaceable =
measurables.fastFirstOrNull { it.layoutId == LeadingId }?.measure(relaxedConstraints)
- occupiedSpaceHorizontally += widthOrZero(leadingPlaceable)
- occupiedSpaceVertically = max(occupiedSpaceVertically, heightOrZero(leadingPlaceable))
+ occupiedSpaceHorizontally += leadingPlaceable.widthOrZero
+ occupiedSpaceVertically = max(occupiedSpaceVertically, leadingPlaceable.heightOrZero)
// measure trailing icon
val trailingPlaceable =
measurables
.fastFirstOrNull { it.layoutId == TrailingId }
?.measure(relaxedConstraints.offset(horizontal = -occupiedSpaceHorizontally))
- occupiedSpaceHorizontally += widthOrZero(trailingPlaceable)
- occupiedSpaceVertically = max(occupiedSpaceVertically, heightOrZero(trailingPlaceable))
+ occupiedSpaceHorizontally += trailingPlaceable.widthOrZero
+ occupiedSpaceVertically = max(occupiedSpaceVertically, trailingPlaceable.heightOrZero)
// measure prefix
val prefixPlaceable =
measurables
.fastFirstOrNull { it.layoutId == PrefixId }
?.measure(relaxedConstraints.offset(horizontal = -occupiedSpaceHorizontally))
- occupiedSpaceHorizontally += widthOrZero(prefixPlaceable)
- occupiedSpaceVertically = max(occupiedSpaceVertically, heightOrZero(prefixPlaceable))
+ occupiedSpaceHorizontally += prefixPlaceable.widthOrZero
+ occupiedSpaceVertically = max(occupiedSpaceVertically, prefixPlaceable.heightOrZero)
// measure suffix
val suffixPlaceable =
measurables
.fastFirstOrNull { it.layoutId == SuffixId }
?.measure(relaxedConstraints.offset(horizontal = -occupiedSpaceHorizontally))
- occupiedSpaceHorizontally += widthOrZero(suffixPlaceable)
- occupiedSpaceVertically = max(occupiedSpaceVertically, heightOrZero(suffixPlaceable))
+ occupiedSpaceHorizontally += suffixPlaceable.widthOrZero
+ occupiedSpaceVertically = max(occupiedSpaceVertically, suffixPlaceable.heightOrZero)
// measure label
val labelHorizontalPaddingOffset =
@@ -834,7 +832,7 @@
-occupiedSpaceHorizontally -
labelHorizontalPaddingOffset, // label in middle
-labelHorizontalPaddingOffset, // label at top
- animationProgress,
+ labelProgress,
),
vertical = -bottomPadding
)
@@ -852,7 +850,7 @@
// measure text field
val topPadding =
- max(heightOrZero(labelPlaceable) / 2, paddingValues.calculateTopPadding().roundToPx())
+ max(labelPlaceable.heightOrZero / 2, paddingValues.calculateTopPadding().roundToPx())
val textConstraints =
constraints
.offset(
@@ -873,24 +871,21 @@
occupiedSpaceVertically =
max(
occupiedSpaceVertically,
- max(heightOrZero(textFieldPlaceable), heightOrZero(placeholderPlaceable)) +
+ max(textFieldPlaceable.heightOrZero, placeholderPlaceable.heightOrZero) +
topPadding +
bottomPadding
)
val width =
calculateWidth(
- leadingPlaceableWidth = widthOrZero(leadingPlaceable),
- trailingPlaceableWidth = widthOrZero(trailingPlaceable),
- prefixPlaceableWidth = widthOrZero(prefixPlaceable),
- suffixPlaceableWidth = widthOrZero(suffixPlaceable),
+ leadingPlaceableWidth = leadingPlaceable.widthOrZero,
+ trailingPlaceableWidth = trailingPlaceable.widthOrZero,
+ prefixPlaceableWidth = prefixPlaceable.widthOrZero,
+ suffixPlaceableWidth = suffixPlaceable.widthOrZero,
textFieldPlaceableWidth = textFieldPlaceable.width,
- labelPlaceableWidth = widthOrZero(labelPlaceable),
- placeholderPlaceableWidth = widthOrZero(placeholderPlaceable),
- animationProgress = animationProgress,
+ labelPlaceableWidth = labelPlaceable.widthOrZero,
+ placeholderPlaceableWidth = placeholderPlaceable.widthOrZero,
constraints = constraints,
- density = density,
- paddingValues = paddingValues,
)
// measure supporting text
@@ -899,22 +894,19 @@
.offset(vertical = -occupiedSpaceVertically)
.copy(minHeight = 0, maxWidth = width)
val supportingPlaceable = supportingMeasurable?.measure(supportingConstraints)
- val supportingHeight = heightOrZero(supportingPlaceable)
+ val supportingHeight = supportingPlaceable.heightOrZero
val totalHeight =
calculateHeight(
- leadingHeight = heightOrZero(leadingPlaceable),
- trailingHeight = heightOrZero(trailingPlaceable),
- prefixHeight = heightOrZero(prefixPlaceable),
- suffixHeight = heightOrZero(suffixPlaceable),
+ leadingHeight = leadingPlaceable.heightOrZero,
+ trailingHeight = trailingPlaceable.heightOrZero,
+ prefixHeight = prefixPlaceable.heightOrZero,
+ suffixHeight = suffixPlaceable.heightOrZero,
textFieldHeight = textFieldPlaceable.height,
- labelHeight = heightOrZero(labelPlaceable),
- placeholderHeight = heightOrZero(placeholderPlaceable),
- supportingHeight = heightOrZero(supportingPlaceable),
- animationProgress = animationProgress,
+ labelHeight = labelPlaceable.heightOrZero,
+ placeholderHeight = placeholderPlaceable.heightOrZero,
+ supportingHeight = supportingPlaceable.heightOrZero,
constraints = constraints,
- density = density,
- paddingValues = paddingValues,
)
val height = totalHeight - supportingHeight
@@ -942,11 +934,8 @@
placeholderPlaceable = placeholderPlaceable,
containerPlaceable = containerPlaceable,
supportingPlaceable = supportingPlaceable,
- animationProgress = animationProgress,
- singleLine = singleLine,
density = density,
layoutDirection = layoutDirection,
- paddingValues = paddingValues,
)
}
}
@@ -1026,10 +1015,7 @@
textFieldPlaceableWidth = textFieldWidth,
labelPlaceableWidth = labelWidth,
placeholderPlaceableWidth = placeholderWidth,
- animationProgress = animationProgress,
- constraints = ZeroConstraints,
- density = density,
- paddingValues = paddingValues,
+ constraints = Constraints(),
)
}
@@ -1044,7 +1030,7 @@
.fastFirstOrNull { it.layoutId == LeadingId }
?.let {
remainingWidth =
- remainingWidth.substractConstraintSafely(
+ remainingWidth.subtractConstraintSafely(
it.maxIntrinsicWidth(Constraints.Infinity)
)
intrinsicMeasurer(it, width)
@@ -1054,7 +1040,7 @@
.fastFirstOrNull { it.layoutId == TrailingId }
?.let {
remainingWidth =
- remainingWidth.substractConstraintSafely(
+ remainingWidth.subtractConstraintSafely(
it.maxIntrinsicWidth(Constraints.Infinity)
)
intrinsicMeasurer(it, width)
@@ -1063,7 +1049,7 @@
val labelHeight =
measurables
.fastFirstOrNull { it.layoutId == LabelId }
- ?.let { intrinsicMeasurer(it, lerp(remainingWidth, width, animationProgress)) } ?: 0
+ ?.let { intrinsicMeasurer(it, lerp(remainingWidth, width, labelProgress)) } ?: 0
val prefixHeight =
measurables
@@ -1071,7 +1057,7 @@
?.let {
val height = intrinsicMeasurer(it, remainingWidth)
remainingWidth =
- remainingWidth.substractConstraintSafely(
+ remainingWidth.subtractConstraintSafely(
it.maxIntrinsicWidth(Constraints.Infinity)
)
height
@@ -1082,7 +1068,7 @@
?.let {
val height = intrinsicMeasurer(it, remainingWidth)
remainingWidth =
- remainingWidth.substractConstraintSafely(
+ remainingWidth.subtractConstraintSafely(
it.maxIntrinsicWidth(Constraints.Infinity)
)
height
@@ -1110,198 +1096,182 @@
labelHeight = labelHeight,
placeholderHeight = placeholderHeight,
supportingHeight = supportingHeight,
- animationProgress = animationProgress,
- constraints = ZeroConstraints,
- density = density,
- paddingValues = paddingValues
+ constraints = Constraints(),
)
}
-}
-private fun Int.substractConstraintSafely(from: Int): Int {
- if (this == Constraints.Infinity) {
- return this
+ /**
+ * Calculate the width of the [OutlinedTextField] given all elements that should be placed
+ * inside.
+ */
+ private fun Density.calculateWidth(
+ leadingPlaceableWidth: Int,
+ trailingPlaceableWidth: Int,
+ prefixPlaceableWidth: Int,
+ suffixPlaceableWidth: Int,
+ textFieldPlaceableWidth: Int,
+ labelPlaceableWidth: Int,
+ placeholderPlaceableWidth: Int,
+ constraints: Constraints,
+ ): Int {
+ val affixTotalWidth = prefixPlaceableWidth + suffixPlaceableWidth
+ val middleSection =
+ maxOf(
+ textFieldPlaceableWidth + affixTotalWidth,
+ placeholderPlaceableWidth + affixTotalWidth,
+ // Prefix/suffix does not get applied to label
+ lerp(labelPlaceableWidth, 0, labelProgress),
+ )
+ val wrappedWidth = leadingPlaceableWidth + middleSection + trailingPlaceableWidth
+
+ // Actual LayoutDirection doesn't matter; we only need the sum
+ val labelHorizontalPadding =
+ (paddingValues.calculateLeftPadding(LayoutDirection.Ltr) +
+ paddingValues.calculateRightPadding(LayoutDirection.Ltr))
+ .toPx()
+ val focusedLabelWidth =
+ ((labelPlaceableWidth + labelHorizontalPadding) * labelProgress).roundToInt()
+ return maxOf(wrappedWidth, focusedLabelWidth, constraints.minWidth)
}
- return this - from
-}
-/**
- * Calculate the width of the [OutlinedTextField] given all elements that should be placed inside.
- */
-private fun calculateWidth(
- leadingPlaceableWidth: Int,
- trailingPlaceableWidth: Int,
- prefixPlaceableWidth: Int,
- suffixPlaceableWidth: Int,
- textFieldPlaceableWidth: Int,
- labelPlaceableWidth: Int,
- placeholderPlaceableWidth: Int,
- animationProgress: Float,
- constraints: Constraints,
- density: Float,
- paddingValues: PaddingValues,
-): Int {
- val affixTotalWidth = prefixPlaceableWidth + suffixPlaceableWidth
- val middleSection =
- maxOf(
- textFieldPlaceableWidth + affixTotalWidth,
- placeholderPlaceableWidth + affixTotalWidth,
- // Prefix/suffix does not get applied to label
- lerp(labelPlaceableWidth, 0, animationProgress),
+ /**
+ * Calculate the height of the [OutlinedTextField] given all elements that should be placed
+ * inside. This includes the supporting text, if it exists, even though this element is not
+ * "visually" inside the text field.
+ */
+ private fun Density.calculateHeight(
+ leadingHeight: Int,
+ trailingHeight: Int,
+ prefixHeight: Int,
+ suffixHeight: Int,
+ textFieldHeight: Int,
+ labelHeight: Int,
+ placeholderHeight: Int,
+ supportingHeight: Int,
+ constraints: Constraints,
+ ): Int {
+ val inputFieldHeight =
+ maxOf(
+ textFieldHeight,
+ placeholderHeight,
+ prefixHeight,
+ suffixHeight,
+ lerp(labelHeight, 0, labelProgress)
+ )
+ val topPadding = paddingValues.calculateTopPadding().toPx()
+ val actualTopPadding = lerp(topPadding, max(topPadding, labelHeight / 2f), labelProgress)
+ val bottomPadding = paddingValues.calculateBottomPadding().toPx()
+ val middleSectionHeight = actualTopPadding + inputFieldHeight + bottomPadding
+
+ return max(
+ constraints.minHeight,
+ maxOf(leadingHeight, trailingHeight, middleSectionHeight.roundToInt()) +
+ supportingHeight
)
- val wrappedWidth = leadingPlaceableWidth + middleSection + trailingPlaceableWidth
+ }
- // Actual LayoutDirection doesn't matter; we only need the sum
- val labelHorizontalPadding =
- (paddingValues.calculateLeftPadding(LayoutDirection.Ltr) +
- paddingValues.calculateRightPadding(LayoutDirection.Ltr))
- .value * density
- val focusedLabelWidth =
- ((labelPlaceableWidth + labelHorizontalPadding) * animationProgress).roundToInt()
- return maxOf(wrappedWidth, focusedLabelWidth, constraints.minWidth)
-}
+ /**
+ * Places the provided text field, placeholder, label, optional leading and trailing icons
+ * inside the [OutlinedTextField]
+ */
+ private fun Placeable.PlacementScope.place(
+ totalHeight: Int,
+ width: Int,
+ leadingPlaceable: Placeable?,
+ trailingPlaceable: Placeable?,
+ prefixPlaceable: Placeable?,
+ suffixPlaceable: Placeable?,
+ textFieldPlaceable: Placeable,
+ labelPlaceable: Placeable?,
+ placeholderPlaceable: Placeable?,
+ containerPlaceable: Placeable,
+ supportingPlaceable: Placeable?,
+ density: Float,
+ layoutDirection: LayoutDirection,
+ ) {
+ // place container
+ containerPlaceable.place(IntOffset.Zero)
-/**
- * Calculate the height of the [OutlinedTextField] given all elements that should be placed inside.
- * This includes the supporting text, if it exists, even though this element is not "visually"
- * inside the text field.
- */
-private fun calculateHeight(
- leadingHeight: Int,
- trailingHeight: Int,
- prefixHeight: Int,
- suffixHeight: Int,
- textFieldHeight: Int,
- labelHeight: Int,
- placeholderHeight: Int,
- supportingHeight: Int,
- animationProgress: Float,
- constraints: Constraints,
- density: Float,
- paddingValues: PaddingValues
-): Int {
- val inputFieldHeight =
- maxOf(
- textFieldHeight,
- placeholderHeight,
- prefixHeight,
- suffixHeight,
- lerp(labelHeight, 0, animationProgress)
+ // Most elements should be positioned w.r.t the text field's "visual" height, i.e.,
+ // excluding
+ // the supporting text on bottom
+ val height = totalHeight - supportingPlaceable.heightOrZero
+ val topPadding = (paddingValues.calculateTopPadding().value * density).roundToInt()
+ val startPadding =
+ (paddingValues.calculateStartPadding(layoutDirection).value * density).roundToInt()
+
+ val iconPadding = HorizontalIconPadding.value * density
+
+ // placed center vertically and to the start edge horizontally
+ leadingPlaceable?.placeRelative(
+ 0,
+ Alignment.CenterVertically.align(leadingPlaceable.height, height)
)
- val topPadding = paddingValues.calculateTopPadding().value * density
- val actualTopPadding = lerp(topPadding, max(topPadding, labelHeight / 2f), animationProgress)
- val bottomPadding = paddingValues.calculateBottomPadding().value * density
- val middleSectionHeight = actualTopPadding + inputFieldHeight + bottomPadding
- return max(
- constraints.minHeight,
- maxOf(leadingHeight, trailingHeight, middleSectionHeight.roundToInt()) + supportingHeight
- )
-}
-
-/**
- * Places the provided text field, placeholder, label, optional leading and trailing icons inside
- * the [OutlinedTextField]
- */
-private fun Placeable.PlacementScope.place(
- totalHeight: Int,
- width: Int,
- leadingPlaceable: Placeable?,
- trailingPlaceable: Placeable?,
- prefixPlaceable: Placeable?,
- suffixPlaceable: Placeable?,
- textFieldPlaceable: Placeable,
- labelPlaceable: Placeable?,
- placeholderPlaceable: Placeable?,
- containerPlaceable: Placeable,
- supportingPlaceable: Placeable?,
- animationProgress: Float,
- singleLine: Boolean,
- density: Float,
- layoutDirection: LayoutDirection,
- paddingValues: PaddingValues
-) {
- // place container
- containerPlaceable.place(IntOffset.Zero)
-
- // Most elements should be positioned w.r.t the text field's "visual" height, i.e., excluding
- // the supporting text on bottom
- val height = totalHeight - heightOrZero(supportingPlaceable)
- val topPadding = (paddingValues.calculateTopPadding().value * density).roundToInt()
- val startPadding =
- (paddingValues.calculateStartPadding(layoutDirection).value * density).roundToInt()
-
- val iconPadding = HorizontalIconPadding.value * density
-
- // placed center vertically and to the start edge horizontally
- leadingPlaceable?.placeRelative(
- 0,
- Alignment.CenterVertically.align(leadingPlaceable.height, height)
- )
-
- // label position is animated
- // in single line text field, label is centered vertically before animation starts
- labelPlaceable?.let {
- val startPositionY =
- if (singleLine) {
- Alignment.CenterVertically.align(it.height, height)
- } else {
- topPadding
- }
- val positionY = lerp(startPositionY, -(it.height / 2), animationProgress)
- val positionX =
- (if (leadingPlaceable == null) {
- 0f
+ // label position is animated
+ // in single line text field, label is centered vertically before animation starts
+ labelPlaceable?.let {
+ val startPositionY =
+ if (singleLine) {
+ Alignment.CenterVertically.align(it.height, height)
} else {
- (widthOrZero(leadingPlaceable) - iconPadding) * (1 - animationProgress)
- })
- .roundToInt() + startPadding
- it.placeRelative(positionX, positionY)
- }
+ topPadding
+ }
+ val positionY = lerp(startPositionY, -(it.height / 2), labelProgress)
+ val positionX =
+ (if (leadingPlaceable == null) {
+ 0f
+ } else {
+ (leadingPlaceable.widthOrZero - iconPadding) * (1 - labelProgress)
+ })
+ .roundToInt() + startPadding
+ it.placeRelative(positionX, positionY)
+ }
- // Single line text fields have text components centered vertically.
- // Multiline text fields have text components aligned to top with padding.
- fun calculateVerticalPosition(placeable: Placeable): Int =
- max(
- if (singleLine) {
- Alignment.CenterVertically.align(placeable.height, height)
- } else {
- topPadding
- },
- heightOrZero(labelPlaceable) / 2
+ // Single line text fields have text components centered vertically.
+ // Multiline text fields have text components aligned to top with padding.
+ fun calculateVerticalPosition(placeable: Placeable): Int =
+ max(
+ if (singleLine) {
+ Alignment.CenterVertically.align(placeable.height, height)
+ } else {
+ topPadding
+ },
+ labelPlaceable.heightOrZero / 2
+ )
+
+ prefixPlaceable?.placeRelative(
+ leadingPlaceable.widthOrZero,
+ calculateVerticalPosition(prefixPlaceable)
)
- prefixPlaceable?.placeRelative(
- widthOrZero(leadingPlaceable),
- calculateVerticalPosition(prefixPlaceable)
- )
+ val textHorizontalPosition = leadingPlaceable.widthOrZero + prefixPlaceable.widthOrZero
- val textHorizontalPosition = widthOrZero(leadingPlaceable) + widthOrZero(prefixPlaceable)
+ textFieldPlaceable.placeRelative(
+ textHorizontalPosition,
+ calculateVerticalPosition(textFieldPlaceable)
+ )
- textFieldPlaceable.placeRelative(
- textHorizontalPosition,
- calculateVerticalPosition(textFieldPlaceable)
- )
+ // placed similar to the input text above
+ placeholderPlaceable?.placeRelative(
+ textHorizontalPosition,
+ calculateVerticalPosition(placeholderPlaceable)
+ )
- // placed similar to the input text above
- placeholderPlaceable?.placeRelative(
- textHorizontalPosition,
- calculateVerticalPosition(placeholderPlaceable)
- )
+ suffixPlaceable?.placeRelative(
+ width - trailingPlaceable.widthOrZero - suffixPlaceable.width,
+ calculateVerticalPosition(suffixPlaceable)
+ )
- suffixPlaceable?.placeRelative(
- width - widthOrZero(trailingPlaceable) - suffixPlaceable.width,
- calculateVerticalPosition(suffixPlaceable)
- )
+ // placed center vertically and to the end edge horizontally
+ trailingPlaceable?.placeRelative(
+ width - trailingPlaceable.width,
+ Alignment.CenterVertically.align(trailingPlaceable.height, height)
+ )
- // placed center vertically and to the end edge horizontally
- trailingPlaceable?.placeRelative(
- width - trailingPlaceable.width,
- Alignment.CenterVertically.align(trailingPlaceable.height, height)
- )
-
- // place supporting text
- supportingPlaceable?.placeRelative(0, height)
+ // place supporting text
+ supportingPlaceable?.placeRelative(0, height)
+ }
}
internal fun Modifier.outlineCutout(labelSize: () -> Size, paddingValues: PaddingValues) =
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
index b3e6fed..5a0242c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
@@ -58,11 +58,11 @@
import androidx.compose.material3.internal.TextFieldId
import androidx.compose.material3.internal.TextFieldLabelExtraPadding
import androidx.compose.material3.internal.TrailingId
-import androidx.compose.material3.internal.ZeroConstraints
import androidx.compose.material3.internal.defaultErrorSemantics
import androidx.compose.material3.internal.getString
import androidx.compose.material3.internal.heightOrZero
import androidx.compose.material3.internal.layoutId
+import androidx.compose.material3.internal.subtractConstraintSafely
import androidx.compose.material3.internal.widthOrZero
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -168,9 +168,12 @@
* be modified. However, a user can focus it and copy text from it. Read-only text fields are
* usually used to display pre-filled forms that a user cannot edit.
* @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle].
+ * @param alwaysMinimizeLabel whether to always minimize the label of this text field. Defaults to
+ * `false`, so the label will expand to occupy the input area when the text field is unfocused and
+ * empty. When `true`, this allows displaying the [placeholder], [prefix], and [suffix] alongside
+ * the [label] when the text field is unfocused and empty.
* @param label the optional label to be displayed with this text field. The default text style uses
- * [Typography.bodySmall] when the text field is in focus and [Typography.bodyLarge] when the text
- * field is not in focus.
+ * [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
* @param placeholder the optional placeholder to be displayed when the input text is empty. The
* default text style uses [Typography.bodyLarge].
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
@@ -231,6 +234,7 @@
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
+ alwaysMinimizeLabel: Boolean = false,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
@@ -295,6 +299,7 @@
lineLimits = lineLimits,
outputTransformation = outputTransformation,
interactionSource = interactionSource,
+ alwaysMinimizeLabel = alwaysMinimizeLabel,
label = label,
placeholder = placeholder,
leadingIcon = leadingIcon,
@@ -346,9 +351,8 @@
* be modified. However, a user can focus it and copy text from it. Read-only text fields are
* usually used to display pre-filled forms that a user cannot edit.
* @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle].
- * @param label the optional label to be displayed inside the text field container. The default text
- * style for internal [Text] is [Typography.bodySmall] when the text field is in focus and
- * [Typography.bodyLarge] when the text field is not in focus
+ * @param label the optional label to be displayed with this text field. The default text style uses
+ * [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the input text is empty. The default text style for internal [Text] is [Typography.bodyLarge]
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
@@ -498,9 +502,8 @@
* be modified. However, a user can focus it and copy text from it. Read-only text fields are
* usually used to display pre-filled forms that a user cannot edit.
* @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle].
- * @param label the optional label to be displayed inside the text field container. The default text
- * style for internal [Text] is [Typography.bodySmall] when the text field is in focus and
- * [Typography.bodyLarge] when the text field is not in focus
+ * @param label the optional label to be displayed with this text field. The default text style uses
+ * [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the input text is empty. The default text style for internal [Text] is [Typography.bodyLarge]
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
@@ -637,14 +640,14 @@
prefix: @Composable (() -> Unit)?,
suffix: @Composable (() -> Unit)?,
singleLine: Boolean,
- animationProgress: Float,
+ labelProgress: Float,
container: @Composable () -> Unit,
supporting: @Composable (() -> Unit)?,
paddingValues: PaddingValues
) {
val measurePolicy =
- remember(singleLine, animationProgress, paddingValues) {
- TextFieldMeasurePolicy(singleLine, animationProgress, paddingValues)
+ remember(singleLine, labelProgress, paddingValues) {
+ TextFieldMeasurePolicy(singleLine, labelProgress, paddingValues)
}
val layoutDirection = LocalLayoutDirection.current
Layout(
@@ -713,12 +716,7 @@
Box(
Modifier.layoutId(LabelId)
.heightIn(
- min =
- lerp(
- MinTextLineHeight,
- MinFocusedLabelLineHeight,
- animationProgress
- )
+ min = lerp(MinTextLineHeight, MinFocusedLabelLineHeight, labelProgress)
)
.wrapContentHeight()
.padding(start = startPadding, end = endPadding)
@@ -763,7 +761,7 @@
private class TextFieldMeasurePolicy(
private val singleLine: Boolean,
- private val animationProgress: Float,
+ private val labelProgress: Float,
private val paddingValues: PaddingValues
) : MeasurePolicy {
override fun MeasureScope.measure(
@@ -781,32 +779,32 @@
// measure leading icon
val leadingPlaceable =
measurables.fastFirstOrNull { it.layoutId == LeadingId }?.measure(looseConstraints)
- occupiedSpaceHorizontally += widthOrZero(leadingPlaceable)
- occupiedSpaceVertically = max(occupiedSpaceVertically, heightOrZero(leadingPlaceable))
+ occupiedSpaceHorizontally += leadingPlaceable.widthOrZero
+ occupiedSpaceVertically = max(occupiedSpaceVertically, leadingPlaceable.heightOrZero)
// measure trailing icon
val trailingPlaceable =
measurables
.fastFirstOrNull { it.layoutId == TrailingId }
?.measure(looseConstraints.offset(horizontal = -occupiedSpaceHorizontally))
- occupiedSpaceHorizontally += widthOrZero(trailingPlaceable)
- occupiedSpaceVertically = max(occupiedSpaceVertically, heightOrZero(trailingPlaceable))
+ occupiedSpaceHorizontally += trailingPlaceable.widthOrZero
+ occupiedSpaceVertically = max(occupiedSpaceVertically, trailingPlaceable.heightOrZero)
// measure prefix
val prefixPlaceable =
measurables
.fastFirstOrNull { it.layoutId == PrefixId }
?.measure(looseConstraints.offset(horizontal = -occupiedSpaceHorizontally))
- occupiedSpaceHorizontally += widthOrZero(prefixPlaceable)
- occupiedSpaceVertically = max(occupiedSpaceVertically, heightOrZero(prefixPlaceable))
+ occupiedSpaceHorizontally += prefixPlaceable.widthOrZero
+ occupiedSpaceVertically = max(occupiedSpaceVertically, prefixPlaceable.heightOrZero)
// measure suffix
val suffixPlaceable =
measurables
.fastFirstOrNull { it.layoutId == SuffixId }
?.measure(looseConstraints.offset(horizontal = -occupiedSpaceHorizontally))
- occupiedSpaceHorizontally += widthOrZero(suffixPlaceable)
- occupiedSpaceVertically = max(occupiedSpaceVertically, heightOrZero(suffixPlaceable))
+ occupiedSpaceHorizontally += suffixPlaceable.widthOrZero
+ occupiedSpaceVertically = max(occupiedSpaceVertically, suffixPlaceable.heightOrZero)
// measure label
val labelConstraints =
@@ -824,7 +822,7 @@
supportingMeasurable?.minIntrinsicHeight(constraints.minWidth) ?: 0
// measure input field
- val effectiveTopOffset = topPaddingValue + heightOrZero(labelPlaceable)
+ val effectiveTopOffset = topPaddingValue + labelPlaceable.heightOrZero
val textFieldConstraints =
constraints
.copy(minHeight = 0)
@@ -845,19 +843,19 @@
occupiedSpaceVertically =
max(
occupiedSpaceVertically,
- max(heightOrZero(textFieldPlaceable), heightOrZero(placeholderPlaceable)) +
+ max(textFieldPlaceable.heightOrZero, placeholderPlaceable.heightOrZero) +
effectiveTopOffset +
bottomPaddingValue
)
val width =
calculateWidth(
- leadingWidth = widthOrZero(leadingPlaceable),
- trailingWidth = widthOrZero(trailingPlaceable),
- prefixWidth = widthOrZero(prefixPlaceable),
- suffixWidth = widthOrZero(suffixPlaceable),
+ leadingWidth = leadingPlaceable.widthOrZero,
+ trailingWidth = trailingPlaceable.widthOrZero,
+ prefixWidth = prefixPlaceable.widthOrZero,
+ suffixWidth = suffixPlaceable.widthOrZero,
textFieldWidth = textFieldPlaceable.width,
- labelWidth = widthOrZero(labelPlaceable),
- placeholderWidth = widthOrZero(placeholderPlaceable),
+ labelWidth = labelPlaceable.widthOrZero,
+ placeholderWidth = placeholderPlaceable.widthOrZero,
constraints = constraints,
)
@@ -867,21 +865,19 @@
.offset(vertical = -occupiedSpaceVertically)
.copy(minHeight = 0, maxWidth = width)
val supportingPlaceable = supportingMeasurable?.measure(supportingConstraints)
- val supportingHeight = heightOrZero(supportingPlaceable)
+ val supportingHeight = supportingPlaceable.heightOrZero
val totalHeight =
calculateHeight(
textFieldHeight = textFieldPlaceable.height,
- labelHeight = heightOrZero(labelPlaceable),
- leadingHeight = heightOrZero(leadingPlaceable),
- trailingHeight = heightOrZero(trailingPlaceable),
- prefixHeight = heightOrZero(prefixPlaceable),
- suffixHeight = heightOrZero(suffixPlaceable),
- placeholderHeight = heightOrZero(placeholderPlaceable),
- supportingHeight = heightOrZero(supportingPlaceable),
- animationProgress = animationProgress,
+ labelHeight = labelPlaceable.heightOrZero,
+ leadingHeight = leadingPlaceable.heightOrZero,
+ trailingHeight = trailingPlaceable.heightOrZero,
+ prefixHeight = prefixPlaceable.heightOrZero,
+ suffixHeight = suffixPlaceable.heightOrZero,
+ placeholderHeight = placeholderPlaceable.heightOrZero,
+ supportingHeight = supportingPlaceable.heightOrZero,
constraints = constraints,
- paddingValues = paddingValues,
)
val height = totalHeight - supportingHeight
@@ -914,11 +910,9 @@
suffixPlaceable = suffixPlaceable,
containerPlaceable = containerPlaceable,
supportingPlaceable = supportingPlaceable,
- singleLine = singleLine,
labelStartPosition = labelStartPosition,
labelEndPosition = topPaddingValue,
textPosition = topPaddingValue + labelPlaceable.height,
- animationProgress = animationProgress,
)
} else {
placeWithoutLabel(
@@ -932,9 +926,7 @@
suffixPlaceable = suffixPlaceable,
containerPlaceable = containerPlaceable,
supportingPlaceable = supportingPlaceable,
- singleLine = singleLine,
density = density,
- paddingValues = paddingValues
)
}
}
@@ -1015,7 +1007,7 @@
textFieldWidth = textFieldWidth,
labelWidth = labelWidth,
placeholderWidth = placeholderWidth,
- constraints = ZeroConstraints
+ constraints = Constraints(),
)
}
@@ -1030,7 +1022,7 @@
.fastFirstOrNull { it.layoutId == LeadingId }
?.let {
remainingWidth =
- remainingWidth.substractConstraintSafely(
+ remainingWidth.subtractConstraintSafely(
it.maxIntrinsicWidth(Constraints.Infinity)
)
intrinsicMeasurer(it, width)
@@ -1040,7 +1032,7 @@
.fastFirstOrNull { it.layoutId == TrailingId }
?.let {
remainingWidth =
- remainingWidth.substractConstraintSafely(
+ remainingWidth.subtractConstraintSafely(
it.maxIntrinsicWidth(Constraints.Infinity)
)
intrinsicMeasurer(it, width)
@@ -1056,7 +1048,7 @@
?.let {
val height = intrinsicMeasurer(it, remainingWidth)
remainingWidth =
- remainingWidth.substractConstraintSafely(
+ remainingWidth.subtractConstraintSafely(
it.maxIntrinsicWidth(Constraints.Infinity)
)
height
@@ -1067,7 +1059,7 @@
?.let {
val height = intrinsicMeasurer(it, remainingWidth)
remainingWidth =
- remainingWidth.substractConstraintSafely(
+ remainingWidth.subtractConstraintSafely(
it.maxIntrinsicWidth(Constraints.Infinity)
)
height
@@ -1094,221 +1086,213 @@
suffixHeight = suffixHeight,
placeholderHeight = placeholderHeight,
supportingHeight = supportingHeight,
- animationProgress = animationProgress,
- constraints = ZeroConstraints,
- paddingValues = paddingValues
+ constraints = Constraints(),
)
}
-}
-private fun Int.substractConstraintSafely(from: Int): Int {
- if (this == Constraints.Infinity) {
- return this
- }
- return this - from
-}
-
-private fun calculateWidth(
- leadingWidth: Int,
- trailingWidth: Int,
- prefixWidth: Int,
- suffixWidth: Int,
- textFieldWidth: Int,
- labelWidth: Int,
- placeholderWidth: Int,
- constraints: Constraints
-): Int {
- val affixTotalWidth = prefixWidth + suffixWidth
- val middleSection =
- maxOf(
- textFieldWidth + affixTotalWidth,
- placeholderWidth + affixTotalWidth,
- // Prefix/suffix does not get applied to label
- labelWidth,
- )
- val wrappedWidth = leadingWidth + middleSection + trailingWidth
- return max(wrappedWidth, constraints.minWidth)
-}
-
-private fun Density.calculateHeight(
- textFieldHeight: Int,
- labelHeight: Int,
- leadingHeight: Int,
- trailingHeight: Int,
- prefixHeight: Int,
- suffixHeight: Int,
- placeholderHeight: Int,
- supportingHeight: Int,
- animationProgress: Float,
- constraints: Constraints,
- paddingValues: PaddingValues
-): Int {
- val verticalPadding =
- (paddingValues.calculateTopPadding() + paddingValues.calculateBottomPadding()).roundToPx()
-
- val inputFieldHeight =
- maxOf(
- textFieldHeight,
- placeholderHeight,
- prefixHeight,
- suffixHeight,
- lerp(labelHeight, 0, animationProgress)
- )
-
- val hasLabel = labelHeight > 0
- val nonOverlappedLabelHeight =
- if (hasLabel) {
- // The label animates from overlapping the input field to floating above it,
- // so its contribution to the height calculation changes over time. Extra padding
- // is added in the unfocused state to keep the height consistent.
- max(
- (TextFieldLabelExtraPadding * 2).roundToPx(),
- lerp(0, labelHeight, animationProgress)
+ private fun calculateWidth(
+ leadingWidth: Int,
+ trailingWidth: Int,
+ prefixWidth: Int,
+ suffixWidth: Int,
+ textFieldWidth: Int,
+ labelWidth: Int,
+ placeholderWidth: Int,
+ constraints: Constraints
+ ): Int {
+ val affixTotalWidth = prefixWidth + suffixWidth
+ val middleSection =
+ maxOf(
+ textFieldWidth + affixTotalWidth,
+ placeholderWidth + affixTotalWidth,
+ // Prefix/suffix does not get applied to label
+ labelWidth,
)
- } else {
- 0
- }
-
- val middleSectionHeight = verticalPadding + nonOverlappedLabelHeight + inputFieldHeight
-
- return max(
- constraints.minHeight,
- maxOf(leadingHeight, trailingHeight, middleSectionHeight) + supportingHeight
- )
-}
-
-/**
- * Places the provided text field, placeholder, and label in the TextField given the PaddingValues
- * when there is a label. When there is no label, [placeWithoutLabel] is used instead.
- */
-private fun Placeable.PlacementScope.placeWithLabel(
- width: Int,
- totalHeight: Int,
- textfieldPlaceable: Placeable,
- labelPlaceable: Placeable,
- placeholderPlaceable: Placeable?,
- leadingPlaceable: Placeable?,
- trailingPlaceable: Placeable?,
- prefixPlaceable: Placeable?,
- suffixPlaceable: Placeable?,
- containerPlaceable: Placeable,
- supportingPlaceable: Placeable?,
- singleLine: Boolean,
- labelStartPosition: Int,
- labelEndPosition: Int,
- textPosition: Int,
- animationProgress: Float,
-) {
- // place container
- containerPlaceable.place(IntOffset.Zero)
-
- // Most elements should be positioned w.r.t the text field's "visual" height, i.e., excluding
- // the supporting text on bottom
- val height = totalHeight - heightOrZero(supportingPlaceable)
-
- leadingPlaceable?.placeRelative(
- 0,
- Alignment.CenterVertically.align(leadingPlaceable.height, height)
- )
-
- val labelY =
- labelPlaceable.let {
- val startPosition =
- if (singleLine) {
- Alignment.CenterVertically.align(it.height, height)
- } else {
- labelStartPosition
- }
- lerp(startPosition, labelEndPosition, animationProgress)
- }
- labelPlaceable.placeRelative(widthOrZero(leadingPlaceable), labelY)
-
- prefixPlaceable?.placeRelative(widthOrZero(leadingPlaceable), textPosition)
-
- val textHorizontalPosition = widthOrZero(leadingPlaceable) + widthOrZero(prefixPlaceable)
- textfieldPlaceable.placeRelative(textHorizontalPosition, textPosition)
- placeholderPlaceable?.placeRelative(textHorizontalPosition, textPosition)
-
- suffixPlaceable?.placeRelative(
- width - widthOrZero(trailingPlaceable) - suffixPlaceable.width,
- textPosition,
- )
-
- trailingPlaceable?.placeRelative(
- width - trailingPlaceable.width,
- Alignment.CenterVertically.align(trailingPlaceable.height, height)
- )
-
- supportingPlaceable?.placeRelative(0, height)
-}
-
-/**
- * Places the provided text field and placeholder in [TextField] when there is no label. When there
- * is a label, [placeWithLabel] is used
- */
-private fun Placeable.PlacementScope.placeWithoutLabel(
- width: Int,
- totalHeight: Int,
- textPlaceable: Placeable,
- placeholderPlaceable: Placeable?,
- leadingPlaceable: Placeable?,
- trailingPlaceable: Placeable?,
- prefixPlaceable: Placeable?,
- suffixPlaceable: Placeable?,
- containerPlaceable: Placeable,
- supportingPlaceable: Placeable?,
- singleLine: Boolean,
- density: Float,
- paddingValues: PaddingValues
-) {
- // place container
- containerPlaceable.place(IntOffset.Zero)
-
- // Most elements should be positioned w.r.t the text field's "visual" height, i.e., excluding
- // the supporting text on bottom
- val height = totalHeight - heightOrZero(supportingPlaceable)
- val topPadding = (paddingValues.calculateTopPadding().value * density).roundToInt()
-
- leadingPlaceable?.placeRelative(
- 0,
- Alignment.CenterVertically.align(leadingPlaceable.height, height)
- )
-
- // Single line text field without label places its text components centered vertically.
- // Multiline text field without label places its text components at the top with padding.
- fun calculateVerticalPosition(placeable: Placeable): Int {
- return if (singleLine) {
- Alignment.CenterVertically.align(placeable.height, height)
- } else {
- topPadding
- }
+ val wrappedWidth = leadingWidth + middleSection + trailingWidth
+ return max(wrappedWidth, constraints.minWidth)
}
- prefixPlaceable?.placeRelative(
- widthOrZero(leadingPlaceable),
- calculateVerticalPosition(prefixPlaceable)
- )
+ private fun Density.calculateHeight(
+ textFieldHeight: Int,
+ labelHeight: Int,
+ leadingHeight: Int,
+ trailingHeight: Int,
+ prefixHeight: Int,
+ suffixHeight: Int,
+ placeholderHeight: Int,
+ supportingHeight: Int,
+ constraints: Constraints,
+ ): Int {
+ val verticalPadding =
+ (paddingValues.calculateTopPadding() + paddingValues.calculateBottomPadding())
+ .roundToPx()
- val textHorizontalPosition = widthOrZero(leadingPlaceable) + widthOrZero(prefixPlaceable)
+ val inputFieldHeight =
+ maxOf(
+ textFieldHeight,
+ placeholderHeight,
+ prefixHeight,
+ suffixHeight,
+ lerp(labelHeight, 0, labelProgress)
+ )
- textPlaceable.placeRelative(textHorizontalPosition, calculateVerticalPosition(textPlaceable))
+ val hasLabel = labelHeight > 0
+ val nonOverlappedLabelHeight =
+ if (hasLabel) {
+ // The label animates from overlapping the input field to floating above it,
+ // so its contribution to the height calculation changes over time. Extra padding
+ // is added in the unfocused state to keep the height consistent.
+ max(
+ (TextFieldLabelExtraPadding * 2).roundToPx(),
+ lerp(0, labelHeight, labelProgress)
+ )
+ } else {
+ 0
+ }
- placeholderPlaceable?.placeRelative(
- textHorizontalPosition,
- calculateVerticalPosition(placeholderPlaceable)
- )
+ val middleSectionHeight = verticalPadding + nonOverlappedLabelHeight + inputFieldHeight
- suffixPlaceable?.placeRelative(
- width - widthOrZero(trailingPlaceable) - suffixPlaceable.width,
- calculateVerticalPosition(suffixPlaceable),
- )
+ return max(
+ constraints.minHeight,
+ maxOf(leadingHeight, trailingHeight, middleSectionHeight) + supportingHeight
+ )
+ }
- trailingPlaceable?.placeRelative(
- width - trailingPlaceable.width,
- Alignment.CenterVertically.align(trailingPlaceable.height, height)
- )
+ /**
+ * Places the provided text field, placeholder, and label in the TextField given the
+ * PaddingValues when there is a label. When there is no label, [placeWithoutLabel] is used
+ * instead.
+ */
+ private fun Placeable.PlacementScope.placeWithLabel(
+ width: Int,
+ totalHeight: Int,
+ textfieldPlaceable: Placeable,
+ labelPlaceable: Placeable,
+ placeholderPlaceable: Placeable?,
+ leadingPlaceable: Placeable?,
+ trailingPlaceable: Placeable?,
+ prefixPlaceable: Placeable?,
+ suffixPlaceable: Placeable?,
+ containerPlaceable: Placeable,
+ supportingPlaceable: Placeable?,
+ labelStartPosition: Int,
+ labelEndPosition: Int,
+ textPosition: Int,
+ ) {
+ // place container
+ containerPlaceable.place(IntOffset.Zero)
- supportingPlaceable?.placeRelative(0, height)
+ // Most elements should be positioned w.r.t the text field's "visual" height, i.e.,
+ // excluding
+ // the supporting text on bottom
+ val height = totalHeight - supportingPlaceable.heightOrZero
+
+ leadingPlaceable?.placeRelative(
+ 0,
+ Alignment.CenterVertically.align(leadingPlaceable.height, height)
+ )
+
+ val labelY =
+ labelPlaceable.let {
+ val startPosition =
+ if (singleLine) {
+ Alignment.CenterVertically.align(it.height, height)
+ } else {
+ labelStartPosition
+ }
+ lerp(startPosition, labelEndPosition, labelProgress)
+ }
+ labelPlaceable.placeRelative(leadingPlaceable.widthOrZero, labelY)
+
+ prefixPlaceable?.placeRelative(leadingPlaceable.widthOrZero, textPosition)
+
+ val textHorizontalPosition = leadingPlaceable.widthOrZero + prefixPlaceable.widthOrZero
+ textfieldPlaceable.placeRelative(textHorizontalPosition, textPosition)
+ placeholderPlaceable?.placeRelative(textHorizontalPosition, textPosition)
+
+ suffixPlaceable?.placeRelative(
+ width - trailingPlaceable.widthOrZero - suffixPlaceable.width,
+ textPosition,
+ )
+
+ trailingPlaceable?.placeRelative(
+ width - trailingPlaceable.width,
+ Alignment.CenterVertically.align(trailingPlaceable.height, height)
+ )
+
+ supportingPlaceable?.placeRelative(0, height)
+ }
+
+ /**
+ * Places the provided text field and placeholder in [TextField] when there is no label. When
+ * there is a label, [placeWithLabel] is used
+ */
+ private fun Placeable.PlacementScope.placeWithoutLabel(
+ width: Int,
+ totalHeight: Int,
+ textPlaceable: Placeable,
+ placeholderPlaceable: Placeable?,
+ leadingPlaceable: Placeable?,
+ trailingPlaceable: Placeable?,
+ prefixPlaceable: Placeable?,
+ suffixPlaceable: Placeable?,
+ containerPlaceable: Placeable,
+ supportingPlaceable: Placeable?,
+ density: Float,
+ ) {
+ // place container
+ containerPlaceable.place(IntOffset.Zero)
+
+ // Most elements should be positioned w.r.t the text field's "visual" height, i.e.,
+ // excluding
+ // the supporting text on bottom
+ val height = totalHeight - supportingPlaceable.heightOrZero
+ val topPadding = (paddingValues.calculateTopPadding().value * density).roundToInt()
+
+ leadingPlaceable?.placeRelative(
+ 0,
+ Alignment.CenterVertically.align(leadingPlaceable.height, height)
+ )
+
+ // Single line text field without label places its text components centered vertically.
+ // Multiline text field without label places its text components at the top with padding.
+ fun calculateVerticalPosition(placeable: Placeable): Int {
+ return if (singleLine) {
+ Alignment.CenterVertically.align(placeable.height, height)
+ } else {
+ topPadding
+ }
+ }
+
+ prefixPlaceable?.placeRelative(
+ leadingPlaceable.widthOrZero,
+ calculateVerticalPosition(prefixPlaceable)
+ )
+
+ val textHorizontalPosition = leadingPlaceable.widthOrZero + prefixPlaceable.widthOrZero
+
+ textPlaceable.placeRelative(
+ textHorizontalPosition,
+ calculateVerticalPosition(textPlaceable)
+ )
+
+ placeholderPlaceable?.placeRelative(
+ textHorizontalPosition,
+ calculateVerticalPosition(placeholderPlaceable)
+ )
+
+ suffixPlaceable?.placeRelative(
+ width - trailingPlaceable.widthOrZero - suffixPlaceable.width,
+ calculateVerticalPosition(suffixPlaceable),
+ )
+
+ trailingPlaceable?.placeRelative(
+ width - trailingPlaceable.width,
+ Alignment.CenterVertically.align(trailingPlaceable.height, height)
+ )
+
+ supportingPlaceable?.placeRelative(0, height)
+ }
}
/** A draw modifier that draws a bottom indicator line in [TextField] */
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
index d1c94dd..9a4352c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
@@ -117,9 +117,12 @@
* [MutableInteractionSource] instance to the [BasicTextField] for it to dispatch events. And
* then pass the same instance to this decorator to observe [Interaction]s and customize the
* appearance/behavior of the text field in different states.
+ * @param alwaysMinimizeLabel whether to always minimize the label of this text field. Defaults
+ * to `false`, so the label will expand to occupy the input area when the text field is
+ * unfocused and empty. When `true`, this allows displaying the [placeholder], [prefix], and
+ * [suffix] alongside the [label] when the text field is unfocused and empty.
* @param label the optional label to be displayed with this text field. The default text style
- * uses [Typography.bodySmall] when the text field is in focus and [Typography.bodyLarge] when
- * the text field is not in focus.
+ * uses [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
* @param placeholder the optional placeholder to be displayed when the input text is empty. The
* default text style uses [Typography.bodyLarge].
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text
@@ -149,6 +152,7 @@
lineLimits: TextFieldLineLimits,
outputTransformation: OutputTransformation?,
interactionSource: InteractionSource,
+ alwaysMinimizeLabel: Boolean = false,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
@@ -193,6 +197,7 @@
visualText = visualText,
innerTextField = innerTextField,
placeholder = placeholder,
+ alwaysMinimizeLabel = alwaysMinimizeLabel,
label = label,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
@@ -346,9 +351,8 @@
* the appearance / behavior of this text field in different states.
* @param isError indicates if the text field's current value is in an error state. When `true`,
* this decoration box will display its contents in an error color.
- * @param label the optional label to be displayed inside the text field container. The default
- * text style for internal [Text] is [Typography.bodySmall] when the text field is in focus
- * and [Typography.bodyLarge] when the text field is not in focus.
+ * @param label the optional label to be displayed with this text field. The default text style
+ * uses [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
* @param placeholder the optional placeholder to be displayed when the text field is in focus
* and the input text is empty. The default text style for internal [Text] is
* [Typography.bodyLarge].
@@ -420,6 +424,7 @@
visualText = visualText,
innerTextField = innerTextField,
placeholder = placeholder,
+ alwaysMinimizeLabel = false,
label = label,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
@@ -923,9 +928,12 @@
* [MutableInteractionSource] instance to the [BasicTextField] for it to dispatch events. And
* then pass the same instance to this decorator to observe [Interaction]s and customize the
* appearance/behavior of the text field in different states.
+ * @param alwaysMinimizeLabel whether to always minimize the label of this text field. Defaults
+ * to `false`, so the label will expand to occupy the input area when the text field is
+ * unfocused and empty. When `true`, this allows displaying the [placeholder], [prefix], and
+ * [suffix] alongside the [label] when the text field is unfocused and empty.
* @param label the optional label to be displayed with this text field. The default text style
- * uses [Typography.bodySmall] when the text field is in focus and [Typography.bodyLarge] when
- * the text field is not in focus.
+ * uses [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
* @param placeholder the optional placeholder to be displayed when the input text is empty. The
* default text style uses [Typography.bodyLarge].
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text
@@ -956,6 +964,7 @@
lineLimits: TextFieldLineLimits,
outputTransformation: OutputTransformation?,
interactionSource: InteractionSource,
+ alwaysMinimizeLabel: Boolean = false,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
@@ -995,6 +1004,7 @@
visualText = visualText,
innerTextField = innerTextField,
placeholder = placeholder,
+ alwaysMinimizeLabel = alwaysMinimizeLabel,
label = label,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
@@ -1096,9 +1106,8 @@
* the appearance / behavior of this text field in different states.
* @param isError indicates if the text field's current value is in an error state. When `true`,
* this decoration box will display its contents in an error color.
- * @param label the optional label to be displayed inside the text field container. The default
- * text style for internal [Text] is [Typography.bodySmall] when the text field is in focus
- * and [Typography.bodyLarge] when the text field is not in focus.
+ * @param label the optional label to be displayed with this text field. The default text style
+ * uses [Typography.bodySmall] when minimized and [Typography.bodyLarge] when expanded.
* @param placeholder the optional placeholder to be displayed when the text field is in focus
* and the input text is empty. The default text style for internal [Text] is
* [Typography.bodyLarge].
@@ -1164,6 +1173,7 @@
visualText = visualText,
innerTextField = innerTextField,
placeholder = placeholder,
+ alwaysMinimizeLabel = false,
label = label,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/LayoutUtil.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/LayoutUtil.kt
index f76cfae2..96f0b5d 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/LayoutUtil.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/LayoutUtil.kt
@@ -19,16 +19,20 @@
import androidx.compose.ui.layout.IntrinsicMeasurable
import androidx.compose.ui.layout.LayoutIdParentData
import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.unit.Constraints
internal val IntrinsicMeasurable.layoutId: Any?
get() = (parentData as? LayoutIdParentData)?.layoutId
-internal fun widthOrZero(placeable: Placeable?) = placeable?.width ?: 0
-
-internal fun heightOrZero(placeable: Placeable?) = placeable?.height ?: 0
-
internal val Placeable?.widthOrZero: Int
get() = this?.width ?: 0
internal val Placeable?.heightOrZero: Int
get() = this?.height ?: 0
+
+internal fun Int.subtractConstraintSafely(other: Int): Int {
+ if (this == Constraints.Infinity) {
+ return this
+ }
+ return this - other
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt
index d4bb175..a12fc29 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/internal/TextFieldImpl.kt
@@ -59,7 +59,6 @@
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.lerp
-import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -74,6 +73,7 @@
type: TextFieldType,
visualText: CharSequence,
innerTextField: @Composable () -> Unit,
+ alwaysMinimizeLabel: Boolean,
label: @Composable (() -> Unit)?,
placeholder: @Composable (() -> Unit)?,
leadingIcon: @Composable (() -> Unit)?,
@@ -117,7 +117,7 @@
if (overrideLabelTextStyleColor) this.takeOrElse { labelColor } else this
},
labelColor = labelColor,
- showLabel = label != null,
+ showExpandedLabel = label != null && !alwaysMinimizeLabel,
) { labelProgress, labelTextStyleColor, labelContentColor, placeholderAlpha, prefixSuffixAlpha
->
val labelProgressValue = labelProgress.value
@@ -232,17 +232,17 @@
supporting = decoratedSupporting,
singleLine = singleLine,
// TODO(b/271000818): progress state read should be deferred to layout phase
- animationProgress = labelProgressValue,
+ labelProgress = labelProgressValue,
paddingValues = contentPadding
)
}
TextFieldType.Outlined -> {
// Outlined cutout
- val labelSize = remember { mutableStateOf(Size.Zero) }
+ val cutoutSize = remember { mutableStateOf(Size.Zero) }
val borderContainerWithId: @Composable () -> Unit = {
Box(
Modifier.layoutId(ContainerId)
- .outlineCutout(labelSize::value, contentPadding),
+ .outlineCutout(cutoutSize::value, contentPadding),
propagateMinConstraints = true
) {
container()
@@ -264,14 +264,14 @@
val labelWidth = it.width * labelProgressValue
val labelHeight = it.height * labelProgressValue
if (
- labelSize.value.width != labelWidth ||
- labelSize.value.height != labelHeight
+ cutoutSize.value.width != labelWidth ||
+ cutoutSize.value.height != labelHeight
) {
- labelSize.value = Size(labelWidth, labelHeight)
+ cutoutSize.value = Size(labelWidth, labelHeight)
}
},
// TODO(b/271000818): progress state read should be deferred to layout phase
- animationProgress = labelProgressValue,
+ labelProgress = labelProgressValue,
container = borderContainerWithId,
paddingValues = contentPadding
)
@@ -316,7 +316,7 @@
focusedLabelTextStyleColor: Color,
unfocusedLabelTextStyleColor: Color,
labelColor: Color,
- showLabel: Boolean,
+ showExpandedLabel: Boolean,
content:
@Composable
(
@@ -339,7 +339,7 @@
) {
when (it) {
InputPhase.Focused -> 1f
- InputPhase.UnfocusedEmpty -> 0f
+ InputPhase.UnfocusedEmpty -> if (showExpandedLabel) 0f else 1f
InputPhase.UnfocusedNotEmpty -> 1f
}
}
@@ -369,7 +369,7 @@
) {
when (it) {
InputPhase.Focused -> 1f
- InputPhase.UnfocusedEmpty -> if (showLabel) 0f else 1f
+ InputPhase.UnfocusedEmpty -> if (showExpandedLabel) 0f else 1f
InputPhase.UnfocusedNotEmpty -> 0f
}
}
@@ -381,7 +381,7 @@
) {
when (it) {
InputPhase.Focused -> 1f
- InputPhase.UnfocusedEmpty -> if (showLabel) 0f else 1f
+ InputPhase.UnfocusedEmpty -> if (showExpandedLabel) 0f else 1f
InputPhase.UnfocusedNotEmpty -> 1f
}
}
@@ -463,7 +463,6 @@
internal const val SuffixId = "Suffix"
internal const val SupportingId = "Supporting"
internal const val ContainerId = "Container"
-internal val ZeroConstraints = Constraints(0, 0, 0, 0)
internal const val TextFieldAnimationDuration = 150
private const val PlaceholderAnimationDuration = 83
diff --git a/compose/runtime/runtime-saveable/build.gradle b/compose/runtime/runtime-saveable/build.gradle
index 45c128e..1365e00 100644
--- a/compose/runtime/runtime-saveable/build.gradle
+++ b/compose/runtime/runtime-saveable/build.gradle
@@ -40,7 +40,7 @@
sourceSets {
commonMain {
dependencies {
- implementation(libs.kotlinStdlibCommon)
+ implementation(libs.kotlinStdlib)
api(project(":compose:runtime:runtime"))
}
}
diff --git a/compose/runtime/runtime-test-utils/build.gradle b/compose/runtime/runtime-test-utils/build.gradle
index 141b480..82a06ac 100644
--- a/compose/runtime/runtime-test-utils/build.gradle
+++ b/compose/runtime/runtime-test-utils/build.gradle
@@ -53,10 +53,14 @@
}
}
+ commonStubsMain {
+ dependsOn(commonMain)
+ }
jvmStubsMain {
- dependsOn(jvmMain)
- dependencies {
- }
+ dependsOn(commonStubsMain)
+ }
+ linuxx64StubsMain {
+ dependsOn(commonStubsMain)
}
}
}
diff --git a/compose/runtime/runtime-test-utils/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/mock/SynchronizedObject.linuxx64Stubs.kt b/compose/runtime/runtime-test-utils/src/commonStubsMain/kotlin/androidx/compose/runtime/mock/SynchronizedObject.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime-test-utils/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/mock/SynchronizedObject.linuxx64Stubs.kt
rename to compose/runtime/runtime-test-utils/src/commonStubsMain/kotlin/androidx/compose/runtime/mock/SynchronizedObject.commonStubs.kt
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index 9618c50..cf49965 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -111,17 +111,20 @@
dependsOn(commonMain)
}
- jvmStubsMain {
- dependsOn(jvmMain)
+ commonStubsMain {
dependsOn(nonAndroidMain)
}
+ jvmStubsMain {
+ dependsOn(commonStubsMain)
+ }
+
jvmStubsTest {
dependsOn(jvmTest)
}
linuxx64StubsMain {
- dependsOn(nonAndroidMain)
+ dependsOn(commonStubsMain)
}
}
}
diff --git a/compose/runtime/runtime/src/jvmStubsMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.jvmStubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/jvmStubsMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.jvmStubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.commonStubs.kt
diff --git a/compose/runtime/runtime/src/jvmStubsMain/kotlin/androidx/compose/runtime/NotImplemented.jvmStubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/NotImplemented.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/jvmStubsMain/kotlin/androidx/compose/runtime/NotImplemented.jvmStubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/NotImplemented.commonStubs.kt
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/SynchronizedObject.linuxx64Stubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/SynchronizedObject.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/SynchronizedObject.linuxx64Stubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/SynchronizedObject.commonStubs.kt
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/TestOnly.linuxx64Stubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/TestOnly.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/TestOnly.linuxx64Stubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/TestOnly.commonStubs.kt
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/Atomic.linuxx64Stubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/Atomic.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/Atomic.linuxx64Stubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/Atomic.commonStubs.kt
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.linuxx64Stubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.linuxx64Stubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/ComposableLambda.commonStubs.kt
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/JvmDefaultWithCompatibility.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/JvmDefaultWithCompatibility.commonStubs.kt
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/Thread.linuxx64Stubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/Thread.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/Thread.linuxx64Stubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/Thread.commonStubs.kt
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/Utils.linuxx64Stubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/Utils.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/Utils.linuxx64Stubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/Utils.commonStubs.kt
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/WeakReference.linuxx64Stubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/WeakReference.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/internal/WeakReference.linuxx64Stubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/internal/WeakReference.commonStubs.kt
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/snapshots/SnapshotContextElement.linuxx64Stubs.kt b/compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/snapshots/SnapshotContextElement.commonStubs.kt
similarity index 100%
rename from compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/snapshots/SnapshotContextElement.linuxx64Stubs.kt
rename to compose/runtime/runtime/src/commonStubsMain/kotlin/androidx/compose/runtime/snapshots/SnapshotContextElement.commonStubs.kt
diff --git a/compose/runtime/runtime/src/jvmStubsMain/kotlin/androidx/compose/runtime/internal/Utils.jvmStubs.kt b/compose/runtime/runtime/src/jvmStubsMain/kotlin/androidx/compose/runtime/internal/Utils.jvmStubs.kt
deleted file mode 100644
index 74c5291..0000000
--- a/compose/runtime/runtime/src/jvmStubsMain/kotlin/androidx/compose/runtime/internal/Utils.jvmStubs.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2024 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.runtime.internal
-
-import androidx.compose.runtime.implementedInJetBrainsFork
-
-internal actual fun logError(message: String, e: Throwable): Unit = implementedInJetBrainsFork()
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.linuxx64Stubs.kt b/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.linuxx64Stubs.kt
deleted file mode 100644
index 2cf85ce..0000000
--- a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/MonotonicFrameClock.linuxx64Stubs.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2024 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.runtime
-
-actual val DefaultMonotonicFrameClock: MonotonicFrameClock
- get() = implementedInJetBrainsFork()
diff --git a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/NotImplemented.linuxx64Stubs.kt b/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/NotImplemented.linuxx64Stubs.kt
deleted file mode 100644
index c1b2c07..0000000
--- a/compose/runtime/runtime/src/linuxx64StubsMain/kotlin/androidx/compose/runtime/NotImplemented.linuxx64Stubs.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2024 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.runtime
-
-@Suppress("NOTHING_TO_INLINE")
-internal inline fun implementedInJetBrainsFork(): Nothing =
- throw NotImplementedError(
- """
- Implemented only in JetBrains fork.
- Please use `org.jetbrains.compose.runtime:runtime` package instead.
- """
- .trimIndent()
- )
diff --git a/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunnerTest.kt b/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunnerTest.kt
index d23b45c..3793dc6 100644
--- a/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunnerTest.kt
+++ b/compose/test-utils/src/androidInstrumentedTest/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunnerTest.kt
@@ -19,6 +19,7 @@
import androidx.activity.ComponentActivity
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -27,12 +28,18 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.focusTarget
+import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -198,6 +205,26 @@
}
@Test
+ fun layout_preservesActiveFocus() {
+ lateinit var focusState: FocusState
+ composeTestRule
+ .forGivenContent {
+ val focusRequester = FocusRequester()
+ Box(
+ Modifier.fillMaxSize()
+ .onFocusChanged { focusState = it }
+ .focusRequester(focusRequester)
+ .focusTarget()
+ )
+ LaunchedEffect(Unit) { focusRequester.requestFocus() }
+ }
+ .performTestWithEventsControl {
+ doFrame()
+ assertThat(focusState.isFocused).isTrue()
+ }
+ }
+
+ @Test
fun countLaunchedCoroutines_noContentLaunches() {
composeTestRule
.forGivenContent { Box { Text("Hello") } }
diff --git a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt
index 89fd282..b7fa4ac 100644
--- a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt
+++ b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt
@@ -226,7 +226,12 @@
"Layout can be only executed after measure, current state is '$simulationState'"
}
val view = getView()
- view.layout(view.left, view.top, view.right, view.bottom)
+ view.layout(
+ /* l= */ 0,
+ /* t= */ 0,
+ /* r= */ view.measuredWidth,
+ /* b= */ view.measuredHeight
+ )
simulationState = SimulationState.LayoutDone
}
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index 3fddc56..e242cff 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -59,8 +59,6 @@
commonTest {
dependencies {
- implementation(libs.junit)
- implementation(libs.truth)
}
}
@@ -81,12 +79,16 @@
}
}
+ commonStubsMain {
+ dependsOn(commonMain)
+ }
+
jvmStubsMain {
- dependsOn(jvmMain)
+ dependsOn(commonStubsMain)
}
linuxx64StubsMain {
- dependsOn(commonMain)
+ dependsOn(commonStubsMain)
}
androidInstrumentedTest {
diff --git a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/AndroidSaversTest.kt b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/AndroidSaversTest.kt
deleted file mode 100644
index b05d927..0000000
--- a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/AndroidSaversTest.kt
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Copyright 2021 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.ui.text
-
-import androidx.compose.runtime.saveable.SaverScope
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontSynthesis
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.intl.Locale
-import androidx.compose.ui.text.intl.LocaleList
-import androidx.compose.ui.text.style.BaselineShift
-import androidx.compose.ui.text.style.LineHeightStyle
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.text.style.TextDirection
-import androidx.compose.ui.text.style.TextGeometricTransform
-import androidx.compose.ui.text.style.TextIndent
-import androidx.compose.ui.unit.TextUnit
-import androidx.compose.ui.unit.em
-import androidx.compose.ui.unit.sp
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-@Suppress("Deprecation")
-class AndroidSaversTest {
- private val defaultSaverScope = SaverScope { true }
-
- @Test
- fun test_TextUnit() {
- val original = 2.sp
- val saved = save(original, TextUnit.Saver, defaultSaverScope)
- val restored: TextUnit? = restore(saved, TextUnit.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_TextUnit_unspecified() {
- val original = TextUnit.Unspecified
- val saved = save(original, TextUnit.Saver, defaultSaverScope)
- val restored: TextUnit? = restore(saved, TextUnit.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_Offset() {
- val original = Offset(10f, 10f)
- val saved = save(original, Offset.Saver, defaultSaverScope)
- val restored: Offset? = restore(saved, Offset.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_Offset_Unspecified() {
- val original = Offset.Unspecified
- val saved = save(original, Offset.Saver, defaultSaverScope)
- val restored: Offset? = restore(saved, Offset.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_Offset_Infinite() {
- val original = Offset.Infinite
- val saved = save(original, Offset.Saver, defaultSaverScope)
- val restored: Offset? = restore(saved, Offset.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_Color() {
- val original = Color.Yellow
- val saved = save(original, Color.Saver, defaultSaverScope)
- val restored: Color? = restore(saved, Color.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_Color_Unspecified() {
- val original = Color.Unspecified
- val saved = save(original, Color.Saver, defaultSaverScope)
- val restored: Color? = restore(saved, Color.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_Shadow() {
- val original = Shadow(color = Color.Blue, offset = Offset(5f, 5f), blurRadius = 2f)
- val saved = save(original, Shadow.Saver, defaultSaverScope)
- val restored: Shadow? = restore(saved, Shadow.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_Shadow_None() {
- val original = Shadow.None
- val saved = save(original, Shadow.Saver, defaultSaverScope)
- val restored: Shadow? = restore(saved, Shadow.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_ParagraphStyle() {
- val original = ParagraphStyle()
- val saved = save(original, ParagraphStyleSaver, defaultSaverScope)
- val restored: ParagraphStyle? = restore(saved, ParagraphStyleSaver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_ParagraphStyle_with_a_nonnull_value() {
- val original = ParagraphStyle(textDirection = TextDirection.Rtl)
- val saved = save(original, ParagraphStyleSaver, defaultSaverScope)
- val restored: ParagraphStyle? = restore(saved, ParagraphStyleSaver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_SpanStyle() {
- val original = SpanStyle()
- val saved = save(original, SpanStyleSaver, defaultSaverScope)
- val restored: SpanStyle? = restore(saved, SpanStyleSaver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_SpanStyle_with_a_nonnull_value() {
- val original = SpanStyle(baselineShift = BaselineShift.Subscript)
- val saved = save(original, SpanStyleSaver, defaultSaverScope)
- val restored: SpanStyle? = restore(saved, SpanStyleSaver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_SpanStyle_with_no_null_value() {
- val original =
- SpanStyle(
- color = Color.Red,
- fontSize = 10.sp,
- fontWeight = FontWeight.Bold,
- fontStyle = FontStyle.Italic,
- fontSynthesis = FontSynthesis.All,
- // fontFamily =
- fontFeatureSettings = "feature settings",
- letterSpacing = 2.em,
- baselineShift = BaselineShift.Superscript,
- textGeometricTransform = TextGeometricTransform(2f, 3f),
- localeList = LocaleList(Locale("sr-Latn-SR"), Locale("sr-Cyrl-SR"), Locale.current),
- background = Color.Blue,
- textDecoration = TextDecoration.LineThrough,
- shadow = Shadow(color = Color.Red, offset = Offset(2f, 2f), blurRadius = 4f)
- )
- val saved = save(original, SpanStyleSaver, defaultSaverScope)
- val restored: SpanStyle? = restore(saved, SpanStyleSaver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_TextLinkStyles() {
- val original = TextLinkStyles(null)
- val saved = save(original, TextLinkStylesSaver, defaultSaverScope)
- val restored: TextLinkStyles? = restore(saved, TextLinkStylesSaver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_TextLinkStyles_withNonNullValues() {
- val original =
- TextLinkStyles(
- SpanStyle(color = Color.Red),
- SpanStyle(color = Color.Green),
- SpanStyle(color = Color.Blue),
- SpanStyle(color = Color.Gray)
- )
- val saved = save(original, TextLinkStylesSaver, defaultSaverScope)
- val restored: TextLinkStyles? = restore(saved, TextLinkStylesSaver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_FontWeight() {
- val original = FontWeight(123)
- val saved = save(original, FontWeight.Saver, defaultSaverScope)
- val restored: FontWeight? = restore(saved, FontWeight.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_FontWeight_w100() {
- val original = FontWeight.W100
- val saved = save(original, FontWeight.Saver, defaultSaverScope)
- val restored: FontWeight? = restore(saved, FontWeight.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_BaselineShift() {
- val original = BaselineShift(2f)
- val saved = save(original, BaselineShift.Saver, defaultSaverScope)
- val restored: BaselineShift? = restore(saved, BaselineShift.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_BaselineShift_None() {
- val original = BaselineShift.None
- val saved = save(original, BaselineShift.Saver, defaultSaverScope)
- val restored: BaselineShift? = restore(saved, BaselineShift.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_TextDecoration() {
- val original =
- TextDecoration.combine(listOf(TextDecoration.LineThrough, TextDecoration.Underline))
- val saved = save(original, TextDecoration.Saver, defaultSaverScope)
- val restored: TextDecoration? = restore(saved, TextDecoration.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_TextDecoration_None() {
- val original = TextDecoration.None
- val saved = save(original, TextDecoration.Saver, defaultSaverScope)
- val restored: TextDecoration? = restore(saved, TextDecoration.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun testSaveRestore_lineThrough() {
- val original = TextDecoration.LineThrough
- val saved = save(original, TextDecoration.Saver, defaultSaverScope)
- val restored: TextDecoration? = restore(saved, TextDecoration.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun testSaveRestore_underline() {
- val original = TextDecoration.Underline
- val saved = save(original, TextDecoration.Saver, defaultSaverScope)
- val restored: TextDecoration? = restore(saved, TextDecoration.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_TextGeometricTransform() {
- val original = TextGeometricTransform(1f, 2f)
- val saved = save(original, TextGeometricTransform.Saver, defaultSaverScope)
- val restored: TextGeometricTransform? = restore(saved, TextGeometricTransform.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_TextGeometricTransform_None() {
- val original = TextGeometricTransform.None
- val saved = save(original, TextGeometricTransform.Saver, defaultSaverScope)
- val restored: TextGeometricTransform? = restore(saved, TextGeometricTransform.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_TextIndent() {
- val original = TextIndent(1.sp, 2.sp)
- val saved = save(original, TextIndent.Saver, defaultSaverScope)
- val restored: TextIndent? = restore(saved, TextIndent.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_TextIndent_None() {
- val original = TextIndent.None
- val saved = save(original, TextIndent.Saver, defaultSaverScope)
- val restored: TextIndent? = restore(saved, TextIndent.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_AnnotatedString() {
- val original = AnnotatedString("abc")
- val saved = with(AnnotatedStringSaver) { defaultSaverScope.save(original) }
-
- assertThat(AnnotatedStringSaver.restore(saved!!)).isEqualTo(original)
- }
-
- @Test
- fun test_AnnotatedString_withSpanStyles() {
- val original = buildAnnotatedString {
- withStyle(SpanStyle(color = Color.Red)) { append("1") }
- withStyle(SpanStyle(fontStyle = FontStyle.Italic)) { append("2") }
- }
-
- val saved = with(AnnotatedStringSaver) { defaultSaverScope.save(original) }
-
- val restored: AnnotatedString = AnnotatedStringSaver.restore(saved!!)!!
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_AnnotatedString_withParagraphStyles() {
- val original = buildAnnotatedString {
- withStyle(ParagraphStyle(textAlign = TextAlign.Justify)) { append("1") }
- withStyle(ParagraphStyle(textDirection = TextDirection.Rtl)) { append("2") }
- }
-
- val saved = with(AnnotatedStringSaver) { defaultSaverScope.save(original) }
-
- val restored: AnnotatedString = AnnotatedStringSaver.restore(saved!!)!!
- assertThat(restored).isEqualTo(original)
- }
-
- @OptIn(ExperimentalTextApi::class)
- @Test
- fun test_AnnotatedString_withAnnotations() {
- val original = buildAnnotatedString {
- withAnnotation(tag = "Tag1", annotation = "Annotation1") { append("1") }
- withAnnotation(VerbatimTtsAnnotation("verbatim1")) { append("2") }
- withAnnotation(tag = "Tag2", annotation = "Annotation2") { append("3") }
- withAnnotation(VerbatimTtsAnnotation("verbatim2")) { append("4") }
- withAnnotation(UrlAnnotation("url1")) { append("5") }
- withAnnotation(UrlAnnotation("url2")) { append("6") }
- withLink(
- LinkAnnotation.Url(
- "url3",
- TextLinkStyles(
- SpanStyle(color = Color.Red),
- SpanStyle(color = Color.Green),
- SpanStyle(color = Color.Blue),
- SpanStyle(color = Color.White)
- )
- )
- ) {
- append("7")
- }
- withLink(
- LinkAnnotation.Clickable(
- "tag3",
- TextLinkStyles(
- SpanStyle(color = Color.Red),
- SpanStyle(color = Color.Green),
- SpanStyle(color = Color.Blue),
- SpanStyle(background = Color.Gray)
- ),
- null
- )
- ) {
- append("8")
- }
- }
-
- val saved = with(AnnotatedStringSaver) { defaultSaverScope.save(original) }
-
- val restored: AnnotatedString = AnnotatedStringSaver.restore(saved!!)!!
-
- assertThat(restored).isEqualTo(original)
- }
-
- @OptIn(ExperimentalTextApi::class)
- @Test
- fun test_AnnotatedString_withSpanAndParagraphStylesAndAnnotations() {
- val original = buildAnnotatedString {
- withStyle(ParagraphStyle(textAlign = TextAlign.Justify)) { append("1") }
- withStyle(ParagraphStyle(textDirection = TextDirection.Rtl)) { append("2") }
- withStyle(SpanStyle(color = Color.Red)) { append("3") }
- withStyle(SpanStyle(fontStyle = FontStyle.Italic)) { append("4") }
- withAnnotation(tag = "Tag1", annotation = "Annotation1") { append("5") }
- withAnnotation(VerbatimTtsAnnotation("verbatim1")) { append("6") }
- withAnnotation(tag = "Tag2", annotation = "Annotation2") { append("7") }
- withAnnotation(VerbatimTtsAnnotation("verbatim2")) { append("8") }
- withAnnotation(UrlAnnotation("url1")) { append("9") }
- withAnnotation(UrlAnnotation("url2")) { append("10") }
- withLink(
- LinkAnnotation.Url(
- "url3",
- TextLinkStyles(
- SpanStyle(color = Color.Red),
- SpanStyle(color = Color.Green),
- SpanStyle(color = Color.Blue),
- SpanStyle(color = Color.Yellow)
- )
- )
- ) {
- append("11")
- }
- withLink(
- LinkAnnotation.Clickable(
- "tag3",
- TextLinkStyles(
- SpanStyle(color = Color.Red),
- SpanStyle(color = Color.Green),
- SpanStyle(color = Color.Blue),
- SpanStyle(color = Color.Gray)
- ),
- null
- )
- ) {
- append("12")
- }
- }
-
- val saved = with(AnnotatedStringSaver) { defaultSaverScope.save(original) }
-
- val restored: AnnotatedString = AnnotatedStringSaver.restore(saved!!)!!
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_Locale() {
- val original = Locale("sr-Latn-SR")
- val saved = with(Locale.Saver) { defaultSaverScope.save(original) }
-
- assertThat(Locale.Saver.restore(saved!!)).isEqualTo(original)
- }
-
- @Test
- fun test_LocaleList() {
- val original = LocaleList(Locale("sr-Latn-SR"), Locale("sr-Cyrl-SR"), Locale.current)
- val saved = with(LocaleList.Saver) { defaultSaverScope.save(original) }
-
- assertThat(LocaleList.Saver.restore(saved!!)).isEqualTo(original)
- }
-
- @Test
- fun test_LineHeightStyle() {
- val original =
- LineHeightStyle(
- LineHeightStyle.Alignment.Proportional,
- LineHeightStyle.Trim.Both,
- LineHeightStyle.Mode.Minimum
- )
- val saved = save(original, LineHeightStyle.Saver, defaultSaverScope)
- val restored: LineHeightStyle? = restore(saved, LineHeightStyle.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-
- @Test
- fun test_PlatformParagraphStyle_with_no_null_args() {
- val original = PlatformParagraphStyle(EmojiSupportMatch.All, true)
- val saved = save(original, PlatformParagraphStyle.Saver, defaultSaverScope)
- val restored: PlatformParagraphStyle? = restore(saved, PlatformParagraphStyle.Saver)
-
- assertThat(restored).isEqualTo(original)
- }
-}
diff --git a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/SaversTest.kt b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/SaversTest.kt
index c6db31c..3465275 100644
--- a/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/SaversTest.kt
+++ b/compose/ui/ui-text/src/androidUnitTest/kotlin/androidx/compose/ui/text/SaversTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * Copyright 2021 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.
@@ -17,13 +17,26 @@
package androidx.compose.ui.text
import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontSynthesis
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.intl.Locale
+import androidx.compose.ui.text.intl.LocaleList
+import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.Hyphens
import androidx.compose.ui.text.style.LineBreak
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextDirection
+import androidx.compose.ui.text.style.TextGeometricTransform
import androidx.compose.ui.text.style.TextIndent
import androidx.compose.ui.text.style.TextMotion
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -31,10 +44,110 @@
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
+@Suppress("Deprecation")
class SaversTest {
private val defaultSaverScope = SaverScope { true }
@Test
+ fun test_TextUnit() {
+ val original = 2.sp
+ val saved = save(original, TextUnit.Saver, defaultSaverScope)
+ val restored: TextUnit? = restore(saved, TextUnit.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_TextUnit_unspecified() {
+ val original = TextUnit.Unspecified
+ val saved = save(original, TextUnit.Saver, defaultSaverScope)
+ val restored: TextUnit? = restore(saved, TextUnit.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_Offset() {
+ val original = Offset(10f, 10f)
+ val saved = save(original, Offset.Saver, defaultSaverScope)
+ val restored: Offset? = restore(saved, Offset.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_Offset_Unspecified() {
+ val original = Offset.Unspecified
+ val saved = save(original, Offset.Saver, defaultSaverScope)
+ val restored: Offset? = restore(saved, Offset.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_Offset_Infinite() {
+ val original = Offset.Infinite
+ val saved = save(original, Offset.Saver, defaultSaverScope)
+ val restored: Offset? = restore(saved, Offset.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_Color() {
+ val original = Color.Yellow
+ val saved = save(original, Color.Saver, defaultSaverScope)
+ val restored: Color? = restore(saved, Color.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_Color_Unspecified() {
+ val original = Color.Unspecified
+ val saved = save(original, Color.Saver, defaultSaverScope)
+ val restored: Color? = restore(saved, Color.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_Shadow() {
+ val original = Shadow(color = Color.Blue, offset = Offset(5f, 5f), blurRadius = 2f)
+ val saved = save(original, Shadow.Saver, defaultSaverScope)
+ val restored: Shadow? = restore(saved, Shadow.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_Shadow_None() {
+ val original = Shadow.None
+ val saved = save(original, Shadow.Saver, defaultSaverScope)
+ val restored: Shadow? = restore(saved, Shadow.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_ParagraphStyle() {
+ val original = ParagraphStyle()
+ val saved = save(original, ParagraphStyleSaver, defaultSaverScope)
+ val restored: ParagraphStyle? = restore(saved, ParagraphStyleSaver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_ParagraphStyle_with_a_nonnull_value() {
+ val original = ParagraphStyle(textDirection = TextDirection.Rtl)
+ val saved = save(original, ParagraphStyleSaver, defaultSaverScope)
+ val restored: ParagraphStyle? = restore(saved, ParagraphStyleSaver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
fun test_ParagraphStyle_with_no_null_value() {
val original =
ParagraphStyle(
@@ -55,6 +168,327 @@
}
@Test
+ fun test_SpanStyle() {
+ val original = SpanStyle()
+ val saved = save(original, SpanStyleSaver, defaultSaverScope)
+ val restored: SpanStyle? = restore(saved, SpanStyleSaver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_SpanStyle_with_a_nonnull_value() {
+ val original = SpanStyle(baselineShift = BaselineShift.Subscript)
+ val saved = save(original, SpanStyleSaver, defaultSaverScope)
+ val restored: SpanStyle? = restore(saved, SpanStyleSaver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_SpanStyle_with_no_null_value() {
+ val original =
+ SpanStyle(
+ color = Color.Red,
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Bold,
+ fontStyle = FontStyle.Italic,
+ fontSynthesis = FontSynthesis.All,
+ // fontFamily =
+ fontFeatureSettings = "feature settings",
+ letterSpacing = 2.em,
+ baselineShift = BaselineShift.Superscript,
+ textGeometricTransform = TextGeometricTransform(2f, 3f),
+ localeList = LocaleList(Locale("sr-Latn-SR"), Locale("sr-Cyrl-SR"), Locale.current),
+ background = Color.Blue,
+ textDecoration = TextDecoration.LineThrough,
+ shadow = Shadow(color = Color.Red, offset = Offset(2f, 2f), blurRadius = 4f)
+ )
+ val saved = save(original, SpanStyleSaver, defaultSaverScope)
+ val restored: SpanStyle? = restore(saved, SpanStyleSaver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_TextLinkStyles() {
+ val original = TextLinkStyles(null)
+ val saved = save(original, TextLinkStylesSaver, defaultSaverScope)
+ val restored: TextLinkStyles? = restore(saved, TextLinkStylesSaver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_TextLinkStyles_withNonNullValues() {
+ val original =
+ TextLinkStyles(
+ SpanStyle(color = Color.Red),
+ SpanStyle(color = Color.Green),
+ SpanStyle(color = Color.Blue),
+ SpanStyle(color = Color.Gray)
+ )
+ val saved = save(original, TextLinkStylesSaver, defaultSaverScope)
+ val restored: TextLinkStyles? = restore(saved, TextLinkStylesSaver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_FontWeight() {
+ val original = FontWeight(123)
+ val saved = save(original, FontWeight.Saver, defaultSaverScope)
+ val restored: FontWeight? = restore(saved, FontWeight.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_FontWeight_w100() {
+ val original = FontWeight.W100
+ val saved = save(original, FontWeight.Saver, defaultSaverScope)
+ val restored: FontWeight? = restore(saved, FontWeight.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_BaselineShift() {
+ val original = BaselineShift(2f)
+ val saved = save(original, BaselineShift.Saver, defaultSaverScope)
+ val restored: BaselineShift? = restore(saved, BaselineShift.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_BaselineShift_None() {
+ val original = BaselineShift.None
+ val saved = save(original, BaselineShift.Saver, defaultSaverScope)
+ val restored: BaselineShift? = restore(saved, BaselineShift.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_TextDecoration() {
+ val original =
+ TextDecoration.combine(listOf(TextDecoration.LineThrough, TextDecoration.Underline))
+ val saved = save(original, TextDecoration.Saver, defaultSaverScope)
+ val restored: TextDecoration? = restore(saved, TextDecoration.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_TextDecoration_None() {
+ val original = TextDecoration.None
+ val saved = save(original, TextDecoration.Saver, defaultSaverScope)
+ val restored: TextDecoration? = restore(saved, TextDecoration.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun testSaveRestore_lineThrough() {
+ val original = TextDecoration.LineThrough
+ val saved = save(original, TextDecoration.Saver, defaultSaverScope)
+ val restored: TextDecoration? = restore(saved, TextDecoration.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun testSaveRestore_underline() {
+ val original = TextDecoration.Underline
+ val saved = save(original, TextDecoration.Saver, defaultSaverScope)
+ val restored: TextDecoration? = restore(saved, TextDecoration.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_TextGeometricTransform() {
+ val original = TextGeometricTransform(1f, 2f)
+ val saved = save(original, TextGeometricTransform.Saver, defaultSaverScope)
+ val restored: TextGeometricTransform? = restore(saved, TextGeometricTransform.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_TextGeometricTransform_None() {
+ val original = TextGeometricTransform.None
+ val saved = save(original, TextGeometricTransform.Saver, defaultSaverScope)
+ val restored: TextGeometricTransform? = restore(saved, TextGeometricTransform.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_TextIndent() {
+ val original = TextIndent(1.sp, 2.sp)
+ val saved = save(original, TextIndent.Saver, defaultSaverScope)
+ val restored: TextIndent? = restore(saved, TextIndent.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_TextIndent_None() {
+ val original = TextIndent.None
+ val saved = save(original, TextIndent.Saver, defaultSaverScope)
+ val restored: TextIndent? = restore(saved, TextIndent.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_AnnotatedString() {
+ val original = AnnotatedString("abc")
+ val saved = with(AnnotatedStringSaver) { defaultSaverScope.save(original) }
+
+ assertThat(AnnotatedStringSaver.restore(saved!!)).isEqualTo(original)
+ }
+
+ @Test
+ fun test_AnnotatedString_withSpanStyles() {
+ val original = buildAnnotatedString {
+ withStyle(SpanStyle(color = Color.Red)) { append("1") }
+ withStyle(SpanStyle(fontStyle = FontStyle.Italic)) { append("2") }
+ }
+
+ val saved = with(AnnotatedStringSaver) { defaultSaverScope.save(original) }
+
+ val restored: AnnotatedString = AnnotatedStringSaver.restore(saved!!)!!
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_AnnotatedString_withParagraphStyles() {
+ val original = buildAnnotatedString {
+ withStyle(ParagraphStyle(textAlign = TextAlign.Justify)) { append("1") }
+ withStyle(ParagraphStyle(textDirection = TextDirection.Rtl)) { append("2") }
+ }
+
+ val saved = with(AnnotatedStringSaver) { defaultSaverScope.save(original) }
+
+ val restored: AnnotatedString = AnnotatedStringSaver.restore(saved!!)!!
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @OptIn(ExperimentalTextApi::class)
+ @Test
+ fun test_AnnotatedString_withAnnotations() {
+ val original = buildAnnotatedString {
+ withAnnotation(tag = "Tag1", annotation = "Annotation1") { append("1") }
+ withAnnotation(VerbatimTtsAnnotation("verbatim1")) { append("2") }
+ withAnnotation(tag = "Tag2", annotation = "Annotation2") { append("3") }
+ withAnnotation(VerbatimTtsAnnotation("verbatim2")) { append("4") }
+ withAnnotation(UrlAnnotation("url1")) { append("5") }
+ withAnnotation(UrlAnnotation("url2")) { append("6") }
+ withLink(
+ LinkAnnotation.Url(
+ "url3",
+ TextLinkStyles(
+ SpanStyle(color = Color.Red),
+ SpanStyle(color = Color.Green),
+ SpanStyle(color = Color.Blue),
+ SpanStyle(color = Color.White)
+ )
+ )
+ ) {
+ append("7")
+ }
+ withLink(
+ LinkAnnotation.Clickable(
+ "tag3",
+ TextLinkStyles(
+ SpanStyle(color = Color.Red),
+ SpanStyle(color = Color.Green),
+ SpanStyle(color = Color.Blue),
+ SpanStyle(background = Color.Gray)
+ ),
+ null
+ )
+ ) {
+ append("8")
+ }
+ }
+
+ val saved = with(AnnotatedStringSaver) { defaultSaverScope.save(original) }
+
+ val restored: AnnotatedString = AnnotatedStringSaver.restore(saved!!)!!
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @OptIn(ExperimentalTextApi::class)
+ @Test
+ fun test_AnnotatedString_withSpanAndParagraphStylesAndAnnotations() {
+ val original = buildAnnotatedString {
+ withStyle(ParagraphStyle(textAlign = TextAlign.Justify)) { append("1") }
+ withStyle(ParagraphStyle(textDirection = TextDirection.Rtl)) { append("2") }
+ withStyle(SpanStyle(color = Color.Red)) { append("3") }
+ withStyle(SpanStyle(fontStyle = FontStyle.Italic)) { append("4") }
+ withAnnotation(tag = "Tag1", annotation = "Annotation1") { append("5") }
+ withAnnotation(VerbatimTtsAnnotation("verbatim1")) { append("6") }
+ withAnnotation(tag = "Tag2", annotation = "Annotation2") { append("7") }
+ withAnnotation(VerbatimTtsAnnotation("verbatim2")) { append("8") }
+ withAnnotation(UrlAnnotation("url1")) { append("9") }
+ withAnnotation(UrlAnnotation("url2")) { append("10") }
+ withLink(
+ LinkAnnotation.Url(
+ "url3",
+ TextLinkStyles(
+ SpanStyle(color = Color.Red),
+ SpanStyle(color = Color.Green),
+ SpanStyle(color = Color.Blue),
+ SpanStyle(color = Color.Yellow)
+ )
+ )
+ ) {
+ append("11")
+ }
+ withLink(
+ LinkAnnotation.Clickable(
+ "tag3",
+ TextLinkStyles(
+ SpanStyle(color = Color.Red),
+ SpanStyle(color = Color.Green),
+ SpanStyle(color = Color.Blue),
+ SpanStyle(color = Color.Gray)
+ ),
+ null
+ )
+ ) {
+ append("12")
+ }
+ }
+
+ val saved = with(AnnotatedStringSaver) { defaultSaverScope.save(original) }
+
+ val restored: AnnotatedString = AnnotatedStringSaver.restore(saved!!)!!
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_Locale() {
+ val original = Locale("sr-Latn-SR")
+ val saved = with(Locale.Saver) { defaultSaverScope.save(original) }
+
+ assertThat(Locale.Saver.restore(saved!!)).isEqualTo(original)
+ }
+
+ @Test
+ fun test_LocaleList() {
+ val original = LocaleList(Locale("sr-Latn-SR"), Locale("sr-Cyrl-SR"), Locale.current)
+ val saved = with(LocaleList.Saver) { defaultSaverScope.save(original) }
+
+ assertThat(LocaleList.Saver.restore(saved!!)).isEqualTo(original)
+ }
+
+ @Test
fun test_PlatformParagraphStyle() {
val original = PlatformParagraphStyle.Default
val saved = save(original, PlatformParagraphStyle.Saver, defaultSaverScope)
@@ -64,6 +498,29 @@
}
@Test
+ fun test_PlatformParagraphStyle_with_no_null_args() {
+ val original = PlatformParagraphStyle(EmojiSupportMatch.All, true)
+ val saved = save(original, PlatformParagraphStyle.Saver, defaultSaverScope)
+ val restored: PlatformParagraphStyle? = restore(saved, PlatformParagraphStyle.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
+ fun test_LineHeightStyle() {
+ val original =
+ LineHeightStyle(
+ LineHeightStyle.Alignment.Proportional,
+ LineHeightStyle.Trim.Both,
+ LineHeightStyle.Mode.Minimum
+ )
+ val saved = save(original, LineHeightStyle.Saver, defaultSaverScope)
+ val restored: LineHeightStyle? = restore(saved, LineHeightStyle.Saver)
+
+ assertThat(restored).isEqualTo(original)
+ }
+
+ @Test
fun test_LineBreak() {
val original = LineBreak.Paragraph
val saved = save(original, LineBreak.Saver, defaultSaverScope)
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineBreak.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineBreak.kt
index 6bbc8bb..b0615de 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineBreak.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/LineBreak.kt
@@ -21,6 +21,7 @@
import androidx.compose.ui.text.style.LineBreak.Companion.Heading
import androidx.compose.ui.text.style.LineBreak.Companion.Paragraph
import androidx.compose.ui.text.style.LineBreak.Companion.Simple
+import kotlin.jvm.JvmInline
/**
* When soft wrap is enabled and the width of the text exceeds the width of its container, line
@@ -40,6 +41,7 @@
*
* @sample androidx.compose.ui.text.samples.AndroidLineBreakSample
*/
+@JvmInline
@Immutable
expect value class LineBreak private constructor(internal val mask: Int) {
companion object {
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/ActualAtomicReferenceJvm.linuxx64Stubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/ActualAtomicReferenceJvm.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/ActualAtomicReferenceJvm.linuxx64Stubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/ActualAtomicReferenceJvm.commonStubs.kt
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/AnnotatedString.linuxx64Stubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/AnnotatedString.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/AnnotatedString.linuxx64Stubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/AnnotatedString.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/NotImplemented.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/NotImplemented.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/NotImplemented.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/NotImplemented.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/Paragraph.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/Paragraph.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/Paragraph.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/Paragraph.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/Savers.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/Savers.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/Savers.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/Savers.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/TextStyle.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/TextStyle.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/TextStyle.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/TextStyle.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.commonStubs.kt
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.linuxx64Stubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.linuxx64Stubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.commonStubs.kt
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/GapBuffer.linuxx64Stubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/input/GapBuffer.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/GapBuffer.linuxx64Stubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/input/GapBuffer.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.commonStubs.kt
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/internal/JvmDefaultWithCompatibility.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/internal/JvmDefaultWithCompatibility.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.commonStubs.kt
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.linuxx64Stubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.linuxx64Stubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/intl/PlatformLocale.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.commonStubs.kt
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/Synchronization.linuxx64Stubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/platform/Synchronization.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/Synchronization.linuxx64Stubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/platform/Synchronization.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.commonStubs.kt
similarity index 97%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.commonStubs.kt
index 9fac85a..0851b71 100644
--- a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.jvmStubs.kt
+++ b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.commonStubs.kt
@@ -19,9 +19,10 @@
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.text.implementedInJetBrainsFork
+import kotlin.jvm.JvmInline
-@Immutable
@JvmInline
+@Immutable
actual value class LineBreak private constructor(internal val mask: Int) {
actual companion object {
@Stable actual val Simple: LineBreak = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.jvmStubs.kt b/compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.jvmStubs.kt
rename to compose/ui/ui-text/src/commonStubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.commonStubs.kt
diff --git a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.jvmStubs.kt b/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.jvmStubs.kt
deleted file mode 100644
index 2aad28e..0000000
--- a/compose/ui/ui-text/src/jvmStubsMain/kotlin/androidx/compose/ui/text/font/FontSynthesis.jvmStubs.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package androidx.compose.ui.text.font
-
-import androidx.compose.ui.text.implementedInJetBrainsFork
-
-/*
- * Copyright 2021 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.
- */
-
-internal actual fun FontSynthesis.synthesizeTypeface(
- typeface: Any,
- font: Font,
- requestedWeight: FontWeight,
- requestedStyle: FontStyle
-): Any = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.linuxx64Stubs.kt
deleted file mode 100644
index 97fc3e3..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/JvmCharHelpers.linuxx64Stubs.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2021 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.ui.text
-
-internal actual fun String.findPrecedingBreak(index: Int): Int = implementedInJetBrainsFork()
-
-internal actual fun String.findFollowingBreak(index: Int): Int = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/NotImplemented.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/NotImplemented.linuxx64Stubs.kt
deleted file mode 100644
index bf9fe98..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/NotImplemented.linuxx64Stubs.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2024 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.ui.text
-
-@Suppress("NOTHING_TO_INLINE")
-internal inline fun implementedInJetBrainsFork(): Nothing =
- throw NotImplementedError(
- """
- Implemented only in JetBrains fork.
- Please use `org.jetbrains.compose.ui:ui-text` package instead.
- """
- .trimIndent()
- )
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Paragraph.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Paragraph.linuxx64Stubs.kt
deleted file mode 100644
index 9781139..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Paragraph.linuxx64Stubs.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2022 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.ui.text
-
-import androidx.annotation.IntRange
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.graphics.drawscope.DrawStyle
-import androidx.compose.ui.text.internal.JvmDefaultWithCompatibility
-import androidx.compose.ui.text.style.ResolvedTextDirection
-import androidx.compose.ui.text.style.TextDecoration
-
-@JvmDefaultWithCompatibility
-actual sealed interface Paragraph {
- actual val width: Float
- actual val height: Float
- actual val minIntrinsicWidth: Float
- actual val maxIntrinsicWidth: Float
- actual val firstBaseline: Float
- actual val lastBaseline: Float
- actual val didExceedMaxLines: Boolean
- actual val lineCount: Int
- actual val placeholderRects: List<Rect?>
-
- actual fun getPathForRange(start: Int, end: Int): Path
-
- actual fun getCursorRect(offset: Int): Rect
-
- actual fun getLineLeft(lineIndex: Int): Float
-
- actual fun getLineRight(lineIndex: Int): Float
-
- actual fun getLineTop(lineIndex: Int): Float
-
- actual fun getLineBaseline(lineIndex: Int): Float
-
- actual fun getLineBottom(lineIndex: Int): Float
-
- actual fun getLineHeight(lineIndex: Int): Float
-
- actual fun getLineWidth(lineIndex: Int): Float
-
- actual fun getLineStart(lineIndex: Int): Int
-
- actual fun getLineEnd(lineIndex: Int, visibleEnd: Boolean): Int
-
- actual fun isLineEllipsized(lineIndex: Int): Boolean
-
- actual fun getLineForOffset(offset: Int): Int
-
- actual fun getHorizontalPosition(offset: Int, usePrimaryDirection: Boolean): Float
-
- actual fun getParagraphDirection(offset: Int): ResolvedTextDirection
-
- actual fun getBidiRunDirection(offset: Int): ResolvedTextDirection
-
- actual fun getLineForVerticalPosition(vertical: Float): Int
-
- actual fun getOffsetForPosition(position: Offset): Int
-
- actual fun getRangeForRect(
- rect: Rect,
- granularity: TextGranularity,
- inclusionStrategy: TextInclusionStrategy
- ): TextRange
-
- actual fun getBoundingBox(offset: Int): Rect
-
- actual fun fillBoundingBoxes(
- range: TextRange,
- array: FloatArray,
- @IntRange(from = 0) arrayStart: Int
- )
-
- actual fun getWordBoundary(offset: Int): TextRange
-
- actual fun paint(canvas: Canvas, color: Color, shadow: Shadow?, textDecoration: TextDecoration?)
-
- actual fun paint(
- canvas: Canvas,
- color: Color,
- shadow: Shadow?,
- textDecoration: TextDecoration?,
- drawStyle: DrawStyle?,
- blendMode: BlendMode
- )
-
- actual fun paint(
- canvas: Canvas,
- brush: Brush,
- alpha: Float,
- shadow: Shadow?,
- textDecoration: TextDecoration?,
- drawStyle: DrawStyle?,
- blendMode: BlendMode
- )
-}
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Savers.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Savers.linuxx64Stubs.kt
deleted file mode 100644
index edec094..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/Savers.linuxx64Stubs.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2024 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.ui.text
-
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.ui.text.style.LineBreak
-import androidx.compose.ui.text.style.TextMotion
-
-internal actual val PlatformParagraphStyle.Companion.Saver: Saver<PlatformParagraphStyle, Any>
- get() = PlatformParagraphStyleSaver
-
-private val PlatformParagraphStyleSaver =
- Saver<PlatformParagraphStyle, Any>(save = {}, restore = { PlatformParagraphStyle() })
-
-internal actual val LineBreak.Companion.Saver: Saver<LineBreak, Any>
- get() = LineBreakSaver
-
-private val LineBreakSaver =
- Saver<LineBreak, Any>(
- save = { it.mask },
- restore = {
- val mask = it as Int
- when (mask) {
- 1 -> LineBreak.Simple
- 2 -> LineBreak.Heading
- 3 -> LineBreak.Paragraph
- else -> {
- LineBreak.Unspecified
- }
- }
- }
- )
-
-internal actual val TextMotion.Companion.Saver: Saver<TextMotion, Any>
- get() = TextMotionSaver
-
-private val TextMotionSaver =
- Saver<TextMotion, Any>(
- save = { if (it == TextMotion.Static) 0 else 1 },
- restore = {
- if (it == 0) {
- TextMotion.Static
- } else {
- TextMotion.Animated
- }
- }
- )
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/TextStyle.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/TextStyle.linuxx64Stubs.kt
deleted file mode 100644
index 55558913..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/TextStyle.linuxx64Stubs.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2022 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.ui.text
-
-internal actual fun createPlatformTextStyle(
- spanStyle: PlatformSpanStyle?,
- paragraphStyle: PlatformParagraphStyle?
-): PlatformTextStyle = implementedInJetBrainsFork()
-
-actual class PlatformTextStyle {
- actual val spanStyle: PlatformSpanStyle?
- get() = implementedInJetBrainsFork()
-
- actual val paragraphStyle: PlatformParagraphStyle?
- get() = implementedInJetBrainsFork()
-}
-
-actual class PlatformParagraphStyle {
- actual companion object {
- actual val Default: PlatformParagraphStyle = implementedInJetBrainsFork()
- }
-
- actual fun merge(other: PlatformParagraphStyle?): PlatformParagraphStyle =
- implementedInJetBrainsFork()
-}
-
-actual class PlatformSpanStyle {
- actual companion object {
- actual val Default: PlatformSpanStyle = implementedInJetBrainsFork()
- }
-
- actual fun merge(other: PlatformSpanStyle?): PlatformSpanStyle = implementedInJetBrainsFork()
-}
-
-actual fun lerp(
- start: PlatformParagraphStyle,
- stop: PlatformParagraphStyle,
- fraction: Float
-): PlatformParagraphStyle = implementedInJetBrainsFork()
-
-actual fun lerp(
- start: PlatformSpanStyle,
- stop: PlatformSpanStyle,
- fraction: Float
-): PlatformSpanStyle = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.linuxx64Stubs.kt
deleted file mode 100644
index 66bdcf0..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/DelegatingFontLoaderForDeprecatedUsage.linuxx64Stubs.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2022 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.ui.text.font
-
-import androidx.compose.ui.text.implementedInJetBrainsFork
-
-internal actual fun createFontFamilyResolver(
- @Suppress("DEPRECATION") fontResourceLoader: Font.ResourceLoader
-): FontFamily.Resolver = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.linuxx64Stubs.kt
deleted file mode 100644
index 17a0131..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/font/PlatformFontFamilyTypefaceAdapter.linuxx64Stubs.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2021 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.ui.text.font
-
-import androidx.compose.ui.text.implementedInJetBrainsFork
-
-internal actual class PlatformFontFamilyTypefaceAdapter actual constructor() :
- FontFamilyTypefaceAdapter {
-
- actual override fun resolve(
- typefaceRequest: TypefaceRequest,
- platformFontLoader: PlatformFontLoader,
- onAsyncCompletion: (TypefaceResult.Immutable) -> Unit,
- createDefaultTypeface: (TypefaceRequest) -> Any
- ): TypefaceResult? = implementedInJetBrainsFork()
-}
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.linuxx64Stubs.kt
deleted file mode 100644
index 2764f82..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/input/PlatformImeOptions.linuxx64Stubs.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2023 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.ui.text.input
-
-import androidx.compose.runtime.Immutable
-
-/** Used to configure the platform specific IME options. */
-@Immutable actual class PlatformImeOptions
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.linuxx64Stubs.kt
deleted file mode 100644
index 018c860..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/intl/DesktopPlatformLocale.linuxx64Stubs.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2020 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.ui.text.intl
-
-import androidx.compose.ui.text.implementedInJetBrainsFork
-
-internal actual fun createPlatformLocaleDelegate(): PlatformLocaleDelegate =
- implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.linuxx64Stubs.kt
deleted file mode 100644
index 2a9df32..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/DesktopStringDelegate.linuxx64Stubs.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2020 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.ui.text.platform
-
-import androidx.compose.ui.text.PlatformStringDelegate
-import androidx.compose.ui.text.implementedInJetBrainsFork
-
-internal actual fun ActualStringDelegate(): PlatformStringDelegate = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.linuxx64Stubs.kt
deleted file mode 100644
index 8f9e6b7..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaMultiParagraphDraw.linuxx64Stubs.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2022 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.ui.text.platform
-
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.Shadow
-import androidx.compose.ui.graphics.drawscope.DrawStyle
-import androidx.compose.ui.text.MultiParagraph
-import androidx.compose.ui.text.implementedInJetBrainsFork
-import androidx.compose.ui.text.style.TextDecoration
-
-internal actual fun MultiParagraph.drawMultiParagraph(
- canvas: Canvas,
- brush: Brush,
- alpha: Float,
- shadow: Shadow?,
- decoration: TextDecoration?,
- drawStyle: DrawStyle?,
- blendMode: BlendMode
-): Unit = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.linuxx64Stubs.kt
deleted file mode 100644
index fb9de57..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraph.linuxx64Stubs.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2020 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.ui.text.platform
-
-import androidx.compose.ui.text.AnnotatedString.Range
-import androidx.compose.ui.text.Paragraph
-import androidx.compose.ui.text.ParagraphIntrinsics
-import androidx.compose.ui.text.Placeholder
-import androidx.compose.ui.text.SpanStyle
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.implementedInJetBrainsFork
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-
-internal actual fun ActualParagraph(
- text: String,
- style: TextStyle,
- spanStyles: List<Range<SpanStyle>>,
- placeholders: List<Range<Placeholder>>,
- maxLines: Int,
- ellipsis: Boolean,
- width: Float,
- density: Density,
- @Suppress("DEPRECATION") resourceLoader: Font.ResourceLoader
-): Paragraph = implementedInJetBrainsFork()
-
-internal actual fun ActualParagraph(
- text: String,
- style: TextStyle,
- spanStyles: List<Range<SpanStyle>>,
- placeholders: List<Range<Placeholder>>,
- maxLines: Int,
- ellipsis: Boolean,
- constraints: Constraints,
- density: Density,
- fontFamilyResolver: FontFamily.Resolver
-): Paragraph = implementedInJetBrainsFork()
-
-internal actual fun ActualParagraph(
- paragraphIntrinsics: ParagraphIntrinsics,
- maxLines: Int,
- ellipsis: Boolean,
- constraints: Constraints
-): Paragraph = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.linuxx64Stubs.kt
deleted file mode 100644
index 8cbb81d..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/platform/SkiaParagraphIntrinsics.linuxx64Stubs.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2020 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.ui.text.platform
-
-import androidx.compose.ui.text.AnnotatedString.Range
-import androidx.compose.ui.text.ParagraphIntrinsics
-import androidx.compose.ui.text.Placeholder
-import androidx.compose.ui.text.SpanStyle
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.implementedInJetBrainsFork
-import androidx.compose.ui.unit.Density
-
-internal actual fun ActualParagraphIntrinsics(
- text: String,
- style: TextStyle,
- spanStyles: List<Range<SpanStyle>>,
- placeholders: List<Range<Placeholder>>,
- density: Density,
- fontFamilyResolver: FontFamily.Resolver
-): ParagraphIntrinsics = implementedInJetBrainsFork()
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.linuxx64Stubs.kt
deleted file mode 100644
index 8a547cd..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/LineBreak.linuxx64Stubs.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2022 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.ui.text.style
-
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.Stable
-import androidx.compose.ui.text.implementedInJetBrainsFork
-
-@Immutable
-actual value class LineBreak private constructor(internal val mask: Int) {
- actual companion object {
- @Stable actual val Simple: LineBreak = implementedInJetBrainsFork()
-
- @Stable actual val Heading: LineBreak = implementedInJetBrainsFork()
-
- @Stable actual val Paragraph: LineBreak = implementedInJetBrainsFork()
-
- @Stable actual val Unspecified: LineBreak = implementedInJetBrainsFork()
- }
-}
diff --git a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.linuxx64Stubs.kt b/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.linuxx64Stubs.kt
deleted file mode 100644
index 2ef3ee0..0000000
--- a/compose/ui/ui-text/src/linuxx64StubsMain/kotlin/androidx/compose/ui/text/style/TextMotion.linuxx64Stubs.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2022 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.ui.text.style
-
-import androidx.compose.runtime.Immutable
-import androidx.compose.ui.text.implementedInJetBrainsFork
-
-@Immutable
-actual class TextMotion private constructor() {
- actual companion object {
- actual val Static: TextMotion = implementedInJetBrainsFork()
-
- actual val Animated: TextMotion = implementedInJetBrainsFork()
- }
-}
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index 57ef847..7f470db 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -70,12 +70,16 @@
}
}
+ commonStubsMain {
+ dependsOn(commonMain)
+ }
+
jvmStubsMain {
- dependsOn(jvmMain)
+ dependsOn(commonStubsMain)
}
linuxx64StubsMain {
- dependsOn(commonMain)
+ dependsOn(commonStubsMain)
}
androidInstrumentedTest {
diff --git a/compose/ui/ui-unit/src/jvmStubsMain/kotlin/androidx/compose/ui/unit/FontScaling.jvmStubs.kt b/compose/ui/ui-unit/src/commonStubsMain/kotlin/androidx/compose/ui/unit/FontScaling.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-unit/src/jvmStubsMain/kotlin/androidx/compose/ui/unit/FontScaling.jvmStubs.kt
rename to compose/ui/ui-unit/src/commonStubsMain/kotlin/androidx/compose/ui/unit/FontScaling.commonStubs.kt
diff --git a/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt b/compose/ui/ui-unit/src/commonStubsMain/kotlin/androidx/compose/ui/unit/internal/JvmDefaultWithCompatibility.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
rename to compose/ui/ui-unit/src/commonStubsMain/kotlin/androidx/compose/ui/unit/internal/JvmDefaultWithCompatibility.commonStubs.kt
diff --git a/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/FontScaling.linuxx64Stubs.kt b/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/FontScaling.linuxx64Stubs.kt
deleted file mode 100644
index 9f1c181..0000000
--- a/compose/ui/ui-unit/src/linuxx64StubsMain/kotlin/androidx/compose/ui/unit/FontScaling.linuxx64Stubs.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2023 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.ui.unit
-
-/** Converts [TextUnit] to [Dp] and vice-versa. */
-actual typealias FontScaling = FontScalingLinear
diff --git a/compose/ui/ui-util/build.gradle b/compose/ui/ui-util/build.gradle
index d07e09f..1abb92c 100644
--- a/compose/ui/ui-util/build.gradle
+++ b/compose/ui/ui-util/build.gradle
@@ -69,7 +69,6 @@
}
jvmStubsMain {
- dependsOn(jvmMain)
dependsOn(commonStubsMain)
}
diff --git a/compose/ui/ui-util/src/linuxx64StubsMain/kotlin/androidx/compose/ui/util/InlineClassHelper.linuxx64Stubs.kt b/compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/InlineClassHelper.commonStubs.kt
similarity index 100%
rename from compose/ui/ui-util/src/linuxx64StubsMain/kotlin/androidx/compose/ui/util/InlineClassHelper.linuxx64Stubs.kt
rename to compose/ui/ui-util/src/commonStubsMain/kotlin/androidx/compose/ui/util/InlineClassHelper.commonStubs.kt
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 0d5ceba..79e19d4 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -107,12 +107,16 @@
}
}
+ commonStubsMain {
+ dependsOn(commonMain)
+ }
+
jvmStubsMain {
- dependsOn(jvmMain)
+ dependsOn(commonStubsMain)
}
linuxx64StubsMain {
- dependsOn(commonMain)
+ dependsOn(commonStubsMain)
}
androidInstrumentedTest {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index bc2bca0..496c90e 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -208,7 +208,7 @@
// flaky, so we use this callback to test accessibility events.
@VisibleForTesting
internal var onSendAccessibilityEvent: (AccessibilityEvent) -> Boolean = {
- view.parent.requestSendAccessibilityEvent(view, it)
+ trace("sendAccessibilityEvent") { view.parent.requestSendAccessibilityEvent(view, it) }
}
private val accessibilityManager: AccessibilityManager =
@@ -1519,7 +1519,7 @@
event.contentDescription = contentDescription.fastJoinToString(",")
}
- return trace("sendEvent") { sendEvent(event) }
+ return sendEvent(event)
}
/**
@@ -2239,47 +2239,45 @@
try {
val subtreeChangedSemanticsNodesIds = MutableIntSet()
for (notification in boundsUpdateChannel) {
- trace("AccessibilityLoopIteration") {
- if (isEnabled) {
- for (i in subtreeChangedLayoutNodes.indices) {
- val layoutNode = subtreeChangedLayoutNodes.valueAt(i)
- trace("sendSubtreeChangeAccessibilityEvents") {
- sendSubtreeChangeAccessibilityEvents(
- layoutNode,
- subtreeChangedSemanticsNodesIds
- )
- }
- trace("sendTypeViewScrolledAccessibilityEvent") {
- sendTypeViewScrolledAccessibilityEvent(layoutNode)
- }
+ if (isEnabled) {
+ for (i in subtreeChangedLayoutNodes.indices) {
+ val layoutNode = subtreeChangedLayoutNodes.valueAt(i)
+ trace("sendSubtreeChangeAccessibilityEvents") {
+ sendSubtreeChangeAccessibilityEvents(
+ layoutNode,
+ subtreeChangedSemanticsNodesIds
+ )
}
- subtreeChangedSemanticsNodesIds.clear()
- // When the bounds of layout nodes change, we will not always get semantics
- // change notifications because bounds is not part of semantics. And bounds
- // change from a layout node without semantics will affect the global bounds
- // of it children which has semantics. Bounds change will affect which nodes
- // are covered and which nodes are not, so the currentSemanticsNodes is not
- // up to date anymore.
- // After the subtree events are sent, accessibility services will get the
- // current visible/invisible state. We also try to do semantics tree diffing
- // to send out the proper accessibility events and update our copy here so
- // that
- // our incremental changes (represented by accessibility events) are
- // consistent
- // with accessibility services. That is: change - notify - new change -
- // notify, if we don't do the tree diffing and update our copy here, we will
- // combine old change and new change, which is missing finer-grained
- // notification.
- if (!checkingForSemanticsChanges) {
- checkingForSemanticsChanges = true
- handler.post(semanticsChangeChecker)
+ trace("sendTypeViewScrolledAccessibilityEvent") {
+ sendTypeViewScrolledAccessibilityEvent(layoutNode)
}
}
- subtreeChangedLayoutNodes.clear()
- pendingHorizontalScrollEvents.clear()
- pendingVerticalScrollEvents.clear()
- delay(SendRecurringAccessibilityEventsIntervalMillis)
+ subtreeChangedSemanticsNodesIds.clear()
+ // When the bounds of layout nodes change, we will not always get semantics
+ // change notifications because bounds is not part of semantics. And bounds
+ // change from a layout node without semantics will affect the global bounds
+ // of it children which has semantics. Bounds change will affect which nodes
+ // are covered and which nodes are not, so the currentSemanticsNodes is not
+ // up to date anymore.
+ // After the subtree events are sent, accessibility services will get the
+ // current visible/invisible state. We also try to do semantics tree diffing
+ // to send out the proper accessibility events and update our copy here so
+ // that
+ // our incremental changes (represented by accessibility events) are
+ // consistent
+ // with accessibility services. That is: change - notify - new change -
+ // notify, if we don't do the tree diffing and update our copy here, we will
+ // combine old change and new change, which is missing finer-grained
+ // notification.
+ if (!checkingForSemanticsChanges) {
+ checkingForSemanticsChanges = true
+ handler.post(semanticsChangeChecker)
+ }
}
+ subtreeChangedLayoutNodes.clear()
+ pendingHorizontalScrollEvents.clear()
+ pendingVerticalScrollEvents.clear()
+ delay(SendRecurringAccessibilityEventsIntervalMillis)
}
} finally {
subtreeChangedLayoutNodes.clear()
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/Actual.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/Actual.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/Actual.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/Actual.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/AtomicReference.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/AtomicReference.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/AtomicReference.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/AtomicReference.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/Modifier.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/Modifier.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/Modifier.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/Modifier.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/NotImplemented.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/NotImplemented.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/NotImplemented.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/NotImplemented.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/autofill/ContentDataType.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/autofill/ContentDataType.commonStubs.kt
similarity index 97%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/autofill/ContentDataType.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/autofill/ContentDataType.commonStubs.kt
index 673ab3a..5db3588 100644
--- a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/autofill/ContentDataType.jvmStubs.kt
+++ b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/autofill/ContentDataType.commonStubs.kt
@@ -17,6 +17,7 @@
package androidx.compose.ui.autofill
import androidx.compose.ui.implementedInJetBrainsFork
+import kotlin.jvm.JvmInline
@JvmInline
internal actual value class ContentDataType actual constructor(val dataType: Int) {
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/autofill/ContentType.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/autofill/ContentType.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/autofill/ContentType.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/autofill/ContentType.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/hapticfeedback/PlatformHapticFeedbackType.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/hapticfeedback/PlatformHapticFeedbackType.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/hapticfeedback/PlatformHapticFeedbackType.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/hapticfeedback/PlatformHapticFeedbackType.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/key/Key.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/key/Key.commonStubs.kt
similarity index 99%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/key/Key.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/key/Key.commonStubs.kt
index 1cf10ac..68211e2 100644
--- a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/key/Key.jvmStubs.kt
+++ b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/key/Key.commonStubs.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.input.key
import androidx.compose.ui.implementedInJetBrainsFork
+import kotlin.jvm.JvmInline
@JvmInline
actual value class Key(val keyCode: Long) {
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/key/KeyEvent.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/key/KeyEvent.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/key/KeyEvent.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/key/KeyEvent.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerEvent.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerEvent.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerEvent.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerEvent.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.commonStubs.kt
similarity index 97%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.commonStubs.kt
index 7aff5d3..ec0970a 100644
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.linuxx64Stubs.kt
+++ b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.commonStubs.kt
@@ -23,4 +23,4 @@
internal actual class PointerInputResetException : CancellationException("Pointer input was reset")
-internal actual object CancelTimeoutCancellationException : CancellationException()
+internal actual object CancelTimeoutCancellationException : CancellationException("")
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/internal/JvmDefaultWithCompatibility.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/internal/JvmDefaultWithCompatibility.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/internal/JvmDefaultWithCompatibility.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/node/JvmTreeSet.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/node/JvmTreeSet.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/node/JvmTreeSet.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/node/JvmTreeSet.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/node/WeakReference.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/node/WeakReference.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/node/WeakReference.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/node/WeakReference.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/AtomicInt.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/AtomicInt.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/AtomicInt.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/AtomicInt.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/ClassHelpers.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/ClassHelpers.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/ClassHelpers.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/ClassHelpers.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/DebugUtils.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/DebugUtils.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/DebugUtils.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/DebugUtils.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputMethodRequest.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputMethodRequest.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputMethodRequest.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputMethodRequest.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputSession.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputSession.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputSession.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputSession.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/Synchronization.linuxx64Stubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/Synchronization.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/Synchronization.linuxx64Stubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/Synchronization.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/platform/Wrapper.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/Wrapper.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/platform/Wrapper.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/Wrapper.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/viewinterop/InteropView.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/viewinterop/InteropView.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/viewinterop/InteropView.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/viewinterop/InteropView.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/viewinterop/InteropViewFactoryHolder.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/viewinterop/InteropViewFactoryHolder.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/viewinterop/InteropViewFactoryHolder.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/viewinterop/InteropViewFactoryHolder.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/window/Dialog.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/window/Dialog.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/window/Dialog.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/window/Dialog.commonStubs.kt
diff --git a/compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/window/Popup.jvmStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/window/Popup.commonStubs.kt
similarity index 100%
rename from compose/ui/ui/src/jvmStubsMain/kotlin/androidx/compose/ui/window/Popup.jvmStubs.kt
rename to compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/window/Popup.commonStubs.kt
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/NotImplemented.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/NotImplemented.linuxx64Stubs.kt
deleted file mode 100644
index e435803..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/NotImplemented.linuxx64Stubs.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2024 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.ui
-
-@Suppress("NOTHING_TO_INLINE")
-internal inline fun implementedInJetBrainsFork(): Nothing =
- throw NotImplementedError(
- """
- Implemented only in JetBrains fork.
- Please use `org.jetbrains.compose.ui:ui` package instead.
- """
- .trimIndent()
- )
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/autofill/ContentDataType.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/autofill/ContentDataType.linuxx64Stubs.kt
deleted file mode 100644
index 6578ef1..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/autofill/ContentDataType.linuxx64Stubs.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2024 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.ui.autofill
-
-import androidx.compose.ui.implementedInJetBrainsFork
-
-internal actual value class ContentDataType actual constructor(val dataType: Int) {
- internal actual companion object {
- actual val Text: ContentDataType = implementedInJetBrainsFork()
- actual val List: ContentDataType = implementedInJetBrainsFork()
- actual val Date: ContentDataType = implementedInJetBrainsFork()
- actual val Toggle: ContentDataType = implementedInJetBrainsFork()
- actual val None: ContentDataType = implementedInJetBrainsFork()
- }
-}
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/autofill/ContentType.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/autofill/ContentType.linuxx64Stubs.kt
deleted file mode 100644
index 96d4e44..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/autofill/ContentType.linuxx64Stubs.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2024 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.ui.autofill
-
-import androidx.compose.ui.implementedInJetBrainsFork
-
-internal actual class ContentType private actual constructor(contentHint: String) {
- internal actual companion object {
- internal actual fun from(value: String): ContentType = implementedInJetBrainsFork()
-
- actual val EmailAddress: ContentType = implementedInJetBrainsFork()
- actual val Username: ContentType = implementedInJetBrainsFork()
- actual val Password: ContentType = implementedInJetBrainsFork()
- actual val NewUsername: ContentType = implementedInJetBrainsFork()
- actual val NewPassword: ContentType = implementedInJetBrainsFork()
- actual val PostalAddress: ContentType = implementedInJetBrainsFork()
- actual val PostalCode: ContentType = implementedInJetBrainsFork()
- actual val CreditCardNumber: ContentType = implementedInJetBrainsFork()
- actual val CreditCardSecurityCode: ContentType = implementedInJetBrainsFork()
- actual val CreditCardExpirationDate: ContentType = implementedInJetBrainsFork()
- actual val CreditCardExpirationMonth: ContentType = implementedInJetBrainsFork()
- actual val CreditCardExpirationYear: ContentType = implementedInJetBrainsFork()
- actual val CreditCardExpirationDay: ContentType = implementedInJetBrainsFork()
- actual val AddressCountry: ContentType = implementedInJetBrainsFork()
- actual val AddressRegion: ContentType = implementedInJetBrainsFork()
- actual val AddressLocality: ContentType = implementedInJetBrainsFork()
- actual val AddressStreet: ContentType = implementedInJetBrainsFork()
- actual val AddressAuxiliaryDetails: ContentType = implementedInJetBrainsFork()
- actual val PostalCodeExtended: ContentType = implementedInJetBrainsFork()
- actual val PersonFullName: ContentType = implementedInJetBrainsFork()
- actual val PersonFirstName: ContentType = implementedInJetBrainsFork()
- actual val PersonLastName: ContentType = implementedInJetBrainsFork()
- actual val PersonMiddleName: ContentType = implementedInJetBrainsFork()
- actual val PersonMiddleInitial: ContentType = implementedInJetBrainsFork()
- actual val PersonNamePrefix: ContentType = implementedInJetBrainsFork()
- actual val PersonNameSuffix: ContentType = implementedInJetBrainsFork()
- actual val PhoneNumber: ContentType = implementedInJetBrainsFork()
- actual val PhoneNumberDevice: ContentType = implementedInJetBrainsFork()
- actual val PhoneCountryCode: ContentType = implementedInJetBrainsFork()
- actual val PhoneNumberNational: ContentType = implementedInJetBrainsFork()
- actual val Gender: ContentType = implementedInJetBrainsFork()
- actual val BirthDateFull: ContentType = implementedInJetBrainsFork()
- actual val BirthDateDay: ContentType = implementedInJetBrainsFork()
- actual val BirthDateMonth: ContentType = implementedInJetBrainsFork()
- actual val BirthDateYear: ContentType = implementedInJetBrainsFork()
- actual val SmsOtpCode: ContentType = implementedInJetBrainsFork()
- }
-}
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.linuxx64Stubs.kt
deleted file mode 100644
index 159832a..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/draganddrop/DragAndDrop.linuxx64Stubs.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2023 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.ui.draganddrop
-
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.implementedInJetBrainsFork
-
-actual class DragAndDropTransferData
-
-actual class DragAndDropEvent
-
-internal actual val DragAndDropEvent.positionInRoot: Offset
- get() = implementedInJetBrainsFork()
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/hapticfeedback/PlatformHapticFeedbackType.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/hapticfeedback/PlatformHapticFeedbackType.linuxx64Stubs.kt
deleted file mode 100644
index fc9fc4c..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/hapticfeedback/PlatformHapticFeedbackType.linuxx64Stubs.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2021 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.ui.hapticfeedback
-
-import androidx.compose.ui.implementedInJetBrainsFork
-
-internal actual object PlatformHapticFeedbackType {
- actual val LongPress: HapticFeedbackType = implementedInJetBrainsFork()
- actual val TextHandleMove: HapticFeedbackType = implementedInJetBrainsFork()
-}
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/key/Key.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/key/Key.linuxx64Stubs.kt
deleted file mode 100644
index b728d29..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/key/Key.linuxx64Stubs.kt
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright 2020 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.ui.input.key
-
-import androidx.compose.ui.implementedInJetBrainsFork
-
-actual value class Key(val keyCode: Long) {
- actual companion object {
- actual val Unknown: Key = implementedInJetBrainsFork()
- actual val Home: Key = implementedInJetBrainsFork()
- actual val Help: Key = implementedInJetBrainsFork()
- actual val DirectionUp: Key = implementedInJetBrainsFork()
- actual val DirectionDown: Key = implementedInJetBrainsFork()
- actual val DirectionLeft: Key = implementedInJetBrainsFork()
- actual val DirectionRight: Key = implementedInJetBrainsFork()
- actual val Zero: Key = implementedInJetBrainsFork()
- actual val One: Key = implementedInJetBrainsFork()
- actual val Two: Key = implementedInJetBrainsFork()
- actual val Three: Key = implementedInJetBrainsFork()
- actual val Four: Key = implementedInJetBrainsFork()
- actual val Five: Key = implementedInJetBrainsFork()
- actual val Six: Key = implementedInJetBrainsFork()
- actual val Seven: Key = implementedInJetBrainsFork()
- actual val Eight: Key = implementedInJetBrainsFork()
- actual val Nine: Key = implementedInJetBrainsFork()
- actual val Plus: Key = implementedInJetBrainsFork()
- actual val Minus: Key = implementedInJetBrainsFork()
- actual val Multiply: Key = implementedInJetBrainsFork()
- actual val Equals: Key = implementedInJetBrainsFork()
- actual val Pound: Key = implementedInJetBrainsFork()
- actual val A: Key = implementedInJetBrainsFork()
- actual val B: Key = implementedInJetBrainsFork()
- actual val C: Key = implementedInJetBrainsFork()
- actual val D: Key = implementedInJetBrainsFork()
- actual val E: Key = implementedInJetBrainsFork()
- actual val F: Key = implementedInJetBrainsFork()
- actual val G: Key = implementedInJetBrainsFork()
- actual val H: Key = implementedInJetBrainsFork()
- actual val I: Key = implementedInJetBrainsFork()
- actual val J: Key = implementedInJetBrainsFork()
- actual val K: Key = implementedInJetBrainsFork()
- actual val L: Key = implementedInJetBrainsFork()
- actual val M: Key = implementedInJetBrainsFork()
- actual val N: Key = implementedInJetBrainsFork()
- actual val O: Key = implementedInJetBrainsFork()
- actual val P: Key = implementedInJetBrainsFork()
- actual val Q: Key = implementedInJetBrainsFork()
- actual val R: Key = implementedInJetBrainsFork()
- actual val S: Key = implementedInJetBrainsFork()
- actual val T: Key = implementedInJetBrainsFork()
- actual val U: Key = implementedInJetBrainsFork()
- actual val V: Key = implementedInJetBrainsFork()
- actual val W: Key = implementedInJetBrainsFork()
- actual val X: Key = implementedInJetBrainsFork()
- actual val Y: Key = implementedInJetBrainsFork()
- actual val Z: Key = implementedInJetBrainsFork()
- actual val Comma: Key = implementedInJetBrainsFork()
- actual val Period: Key = implementedInJetBrainsFork()
- actual val AltLeft: Key = implementedInJetBrainsFork()
- actual val AltRight: Key = implementedInJetBrainsFork()
- actual val ShiftLeft: Key = implementedInJetBrainsFork()
- actual val ShiftRight: Key = implementedInJetBrainsFork()
- actual val Tab: Key = implementedInJetBrainsFork()
- actual val Spacebar: Key = implementedInJetBrainsFork()
- actual val Enter: Key = implementedInJetBrainsFork()
- actual val Backspace: Key = implementedInJetBrainsFork()
- actual val Delete: Key = implementedInJetBrainsFork()
- actual val Escape: Key = implementedInJetBrainsFork()
- actual val CtrlLeft: Key = implementedInJetBrainsFork()
- actual val CtrlRight: Key = implementedInJetBrainsFork()
- actual val CapsLock: Key = implementedInJetBrainsFork()
- actual val ScrollLock: Key = implementedInJetBrainsFork()
- actual val MetaLeft: Key = implementedInJetBrainsFork()
- actual val MetaRight: Key = implementedInJetBrainsFork()
- actual val PrintScreen: Key = implementedInJetBrainsFork()
- actual val Insert: Key = implementedInJetBrainsFork()
- actual val Cut: Key = implementedInJetBrainsFork()
- actual val Copy: Key = implementedInJetBrainsFork()
- actual val Paste: Key = implementedInJetBrainsFork()
- actual val Grave: Key = implementedInJetBrainsFork()
- actual val LeftBracket: Key = implementedInJetBrainsFork()
- actual val RightBracket: Key = implementedInJetBrainsFork()
- actual val Slash: Key = implementedInJetBrainsFork()
- actual val Backslash: Key = implementedInJetBrainsFork()
- actual val Semicolon: Key = implementedInJetBrainsFork()
- actual val Apostrophe: Key = implementedInJetBrainsFork()
- actual val At: Key = implementedInJetBrainsFork()
- actual val PageUp: Key = implementedInJetBrainsFork()
- actual val PageDown: Key = implementedInJetBrainsFork()
- actual val F1: Key = implementedInJetBrainsFork()
- actual val F2: Key = implementedInJetBrainsFork()
- actual val F3: Key = implementedInJetBrainsFork()
- actual val F4: Key = implementedInJetBrainsFork()
- actual val F5: Key = implementedInJetBrainsFork()
- actual val F6: Key = implementedInJetBrainsFork()
- actual val F7: Key = implementedInJetBrainsFork()
- actual val F8: Key = implementedInJetBrainsFork()
- actual val F9: Key = implementedInJetBrainsFork()
- actual val F10: Key = implementedInJetBrainsFork()
- actual val F11: Key = implementedInJetBrainsFork()
- actual val F12: Key = implementedInJetBrainsFork()
- actual val NumLock: Key = implementedInJetBrainsFork()
- actual val NumPad0: Key = implementedInJetBrainsFork()
- actual val NumPad1: Key = implementedInJetBrainsFork()
- actual val NumPad2: Key = implementedInJetBrainsFork()
- actual val NumPad3: Key = implementedInJetBrainsFork()
- actual val NumPad4: Key = implementedInJetBrainsFork()
- actual val NumPad5: Key = implementedInJetBrainsFork()
- actual val NumPad6: Key = implementedInJetBrainsFork()
- actual val NumPad7: Key = implementedInJetBrainsFork()
- actual val NumPad8: Key = implementedInJetBrainsFork()
- actual val NumPad9: Key = implementedInJetBrainsFork()
- actual val NumPadDivide: Key = implementedInJetBrainsFork()
- actual val NumPadMultiply: Key = implementedInJetBrainsFork()
- actual val NumPadSubtract: Key = implementedInJetBrainsFork()
- actual val NumPadAdd: Key = implementedInJetBrainsFork()
- actual val NumPadDot: Key = implementedInJetBrainsFork()
- actual val NumPadComma: Key = implementedInJetBrainsFork()
- actual val NumPadEnter: Key = implementedInJetBrainsFork()
- actual val NumPadEquals: Key = implementedInJetBrainsFork()
- actual val NumPadLeftParenthesis: Key = implementedInJetBrainsFork()
- actual val NumPadRightParenthesis: Key = implementedInJetBrainsFork()
- actual val MoveHome: Key = implementedInJetBrainsFork()
- actual val MoveEnd: Key = implementedInJetBrainsFork()
- actual val SoftLeft: Key = implementedInJetBrainsFork()
- actual val SoftRight: Key = implementedInJetBrainsFork()
- actual val Back: Key = implementedInJetBrainsFork()
- actual val NavigatePrevious: Key = implementedInJetBrainsFork()
- actual val NavigateNext: Key = implementedInJetBrainsFork()
- actual val NavigateIn: Key = implementedInJetBrainsFork()
- actual val NavigateOut: Key = implementedInJetBrainsFork()
- actual val SystemNavigationUp: Key = implementedInJetBrainsFork()
- actual val SystemNavigationDown: Key = implementedInJetBrainsFork()
- actual val SystemNavigationLeft: Key = implementedInJetBrainsFork()
- actual val SystemNavigationRight: Key = implementedInJetBrainsFork()
- actual val Call: Key = implementedInJetBrainsFork()
- actual val EndCall: Key = implementedInJetBrainsFork()
- actual val DirectionCenter: Key = implementedInJetBrainsFork()
- actual val DirectionUpLeft: Key = implementedInJetBrainsFork()
- actual val DirectionDownLeft: Key = implementedInJetBrainsFork()
- actual val DirectionUpRight: Key = implementedInJetBrainsFork()
- actual val DirectionDownRight: Key = implementedInJetBrainsFork()
- actual val VolumeUp: Key = implementedInJetBrainsFork()
- actual val VolumeDown: Key = implementedInJetBrainsFork()
- actual val Power: Key = implementedInJetBrainsFork()
- actual val Camera: Key = implementedInJetBrainsFork()
- actual val Clear: Key = implementedInJetBrainsFork()
- actual val Symbol: Key = implementedInJetBrainsFork()
- actual val Browser: Key = implementedInJetBrainsFork()
- actual val Envelope: Key = implementedInJetBrainsFork()
- actual val Function: Key = implementedInJetBrainsFork()
- actual val Break: Key = implementedInJetBrainsFork()
- actual val Number: Key = implementedInJetBrainsFork()
- actual val HeadsetHook: Key = implementedInJetBrainsFork()
- actual val Focus: Key = implementedInJetBrainsFork()
- actual val Menu: Key = implementedInJetBrainsFork()
- actual val Notification: Key = implementedInJetBrainsFork()
- actual val Search: Key = implementedInJetBrainsFork()
- actual val PictureSymbols: Key = implementedInJetBrainsFork()
- actual val SwitchCharset: Key = implementedInJetBrainsFork()
- actual val ButtonA: Key = implementedInJetBrainsFork()
- actual val ButtonB: Key = implementedInJetBrainsFork()
- actual val ButtonC: Key = implementedInJetBrainsFork()
- actual val ButtonX: Key = implementedInJetBrainsFork()
- actual val ButtonY: Key = implementedInJetBrainsFork()
- actual val ButtonZ: Key = implementedInJetBrainsFork()
- actual val ButtonL1: Key = implementedInJetBrainsFork()
- actual val ButtonR1: Key = implementedInJetBrainsFork()
- actual val ButtonL2: Key = implementedInJetBrainsFork()
- actual val ButtonR2: Key = implementedInJetBrainsFork()
- actual val ButtonThumbLeft: Key = implementedInJetBrainsFork()
- actual val ButtonThumbRight: Key = implementedInJetBrainsFork()
- actual val ButtonStart: Key = implementedInJetBrainsFork()
- actual val ButtonSelect: Key = implementedInJetBrainsFork()
- actual val ButtonMode: Key = implementedInJetBrainsFork()
- actual val Button1: Key = implementedInJetBrainsFork()
- actual val Button2: Key = implementedInJetBrainsFork()
- actual val Button3: Key = implementedInJetBrainsFork()
- actual val Button4: Key = implementedInJetBrainsFork()
- actual val Button5: Key = implementedInJetBrainsFork()
- actual val Button6: Key = implementedInJetBrainsFork()
- actual val Button7: Key = implementedInJetBrainsFork()
- actual val Button8: Key = implementedInJetBrainsFork()
- actual val Button9: Key = implementedInJetBrainsFork()
- actual val Button10: Key = implementedInJetBrainsFork()
- actual val Button11: Key = implementedInJetBrainsFork()
- actual val Button12: Key = implementedInJetBrainsFork()
- actual val Button13: Key = implementedInJetBrainsFork()
- actual val Button14: Key = implementedInJetBrainsFork()
- actual val Button15: Key = implementedInJetBrainsFork()
- actual val Button16: Key = implementedInJetBrainsFork()
- actual val Forward: Key = implementedInJetBrainsFork()
- actual val MediaPlay: Key = implementedInJetBrainsFork()
- actual val MediaPause: Key = implementedInJetBrainsFork()
- actual val MediaPlayPause: Key = implementedInJetBrainsFork()
- actual val MediaStop: Key = implementedInJetBrainsFork()
- actual val MediaRecord: Key = implementedInJetBrainsFork()
- actual val MediaNext: Key = implementedInJetBrainsFork()
- actual val MediaPrevious: Key = implementedInJetBrainsFork()
- actual val MediaRewind: Key = implementedInJetBrainsFork()
- actual val MediaFastForward: Key = implementedInJetBrainsFork()
- actual val MediaClose: Key = implementedInJetBrainsFork()
- actual val MediaAudioTrack: Key = implementedInJetBrainsFork()
- actual val MediaEject: Key = implementedInJetBrainsFork()
- actual val MediaTopMenu: Key = implementedInJetBrainsFork()
- actual val MediaSkipForward: Key = implementedInJetBrainsFork()
- actual val MediaSkipBackward: Key = implementedInJetBrainsFork()
- actual val MediaStepForward: Key = implementedInJetBrainsFork()
- actual val MediaStepBackward: Key = implementedInJetBrainsFork()
- actual val MicrophoneMute: Key = implementedInJetBrainsFork()
- actual val VolumeMute: Key = implementedInJetBrainsFork()
- actual val Info: Key = implementedInJetBrainsFork()
- actual val ChannelUp: Key = implementedInJetBrainsFork()
- actual val ChannelDown: Key = implementedInJetBrainsFork()
- actual val ZoomIn: Key = implementedInJetBrainsFork()
- actual val ZoomOut: Key = implementedInJetBrainsFork()
- actual val Tv: Key = implementedInJetBrainsFork()
- actual val Window: Key = implementedInJetBrainsFork()
- actual val Guide: Key = implementedInJetBrainsFork()
- actual val Dvr: Key = implementedInJetBrainsFork()
- actual val Bookmark: Key = implementedInJetBrainsFork()
- actual val Captions: Key = implementedInJetBrainsFork()
- actual val Settings: Key = implementedInJetBrainsFork()
- actual val TvPower: Key = implementedInJetBrainsFork()
- actual val TvInput: Key = implementedInJetBrainsFork()
- actual val SetTopBoxPower: Key = implementedInJetBrainsFork()
- actual val SetTopBoxInput: Key = implementedInJetBrainsFork()
- actual val AvReceiverPower: Key = implementedInJetBrainsFork()
- actual val AvReceiverInput: Key = implementedInJetBrainsFork()
- actual val ProgramRed: Key = implementedInJetBrainsFork()
- actual val ProgramGreen: Key = implementedInJetBrainsFork()
- actual val ProgramYellow: Key = implementedInJetBrainsFork()
- actual val ProgramBlue: Key = implementedInJetBrainsFork()
- actual val AppSwitch: Key = implementedInJetBrainsFork()
- actual val LanguageSwitch: Key = implementedInJetBrainsFork()
- actual val MannerMode: Key = implementedInJetBrainsFork()
- actual val Toggle2D3D: Key = implementedInJetBrainsFork()
- actual val Contacts: Key = implementedInJetBrainsFork()
- actual val Calendar: Key = implementedInJetBrainsFork()
- actual val Music: Key = implementedInJetBrainsFork()
- actual val Calculator: Key = implementedInJetBrainsFork()
- actual val ZenkakuHankaru: Key = implementedInJetBrainsFork()
- actual val Eisu: Key = implementedInJetBrainsFork()
- actual val Muhenkan: Key = implementedInJetBrainsFork()
- actual val Henkan: Key = implementedInJetBrainsFork()
- actual val KatakanaHiragana: Key = implementedInJetBrainsFork()
- actual val Yen: Key = implementedInJetBrainsFork()
- actual val Ro: Key = implementedInJetBrainsFork()
- actual val Kana: Key = implementedInJetBrainsFork()
- actual val Assist: Key = implementedInJetBrainsFork()
- actual val BrightnessDown: Key = implementedInJetBrainsFork()
- actual val BrightnessUp: Key = implementedInJetBrainsFork()
- actual val Sleep: Key = implementedInJetBrainsFork()
- actual val WakeUp: Key = implementedInJetBrainsFork()
- actual val SoftSleep: Key = implementedInJetBrainsFork()
- actual val Pairing: Key = implementedInJetBrainsFork()
- actual val LastChannel: Key = implementedInJetBrainsFork()
- actual val TvDataService: Key = implementedInJetBrainsFork()
- actual val VoiceAssist: Key = implementedInJetBrainsFork()
- actual val TvRadioService: Key = implementedInJetBrainsFork()
- actual val TvTeletext: Key = implementedInJetBrainsFork()
- actual val TvNumberEntry: Key = implementedInJetBrainsFork()
- actual val TvTerrestrialAnalog: Key = implementedInJetBrainsFork()
- actual val TvTerrestrialDigital: Key = implementedInJetBrainsFork()
- actual val TvSatellite: Key = implementedInJetBrainsFork()
- actual val TvSatelliteBs: Key = implementedInJetBrainsFork()
- actual val TvSatelliteCs: Key = implementedInJetBrainsFork()
- actual val TvSatelliteService: Key = implementedInJetBrainsFork()
- actual val TvNetwork: Key = implementedInJetBrainsFork()
- actual val TvAntennaCable: Key = implementedInJetBrainsFork()
- actual val TvInputHdmi1: Key = implementedInJetBrainsFork()
- actual val TvInputHdmi2: Key = implementedInJetBrainsFork()
- actual val TvInputHdmi3: Key = implementedInJetBrainsFork()
- actual val TvInputHdmi4: Key = implementedInJetBrainsFork()
- actual val TvInputComposite1: Key = implementedInJetBrainsFork()
- actual val TvInputComposite2: Key = implementedInJetBrainsFork()
- actual val TvInputComponent1: Key = implementedInJetBrainsFork()
- actual val TvInputComponent2: Key = implementedInJetBrainsFork()
- actual val TvInputVga1: Key = implementedInJetBrainsFork()
- actual val TvAudioDescription: Key = implementedInJetBrainsFork()
- actual val TvAudioDescriptionMixingVolumeUp: Key = implementedInJetBrainsFork()
- actual val TvAudioDescriptionMixingVolumeDown: Key = implementedInJetBrainsFork()
- actual val TvZoomMode: Key = implementedInJetBrainsFork()
- actual val TvContentsMenu: Key = implementedInJetBrainsFork()
- actual val TvMediaContextMenu: Key = implementedInJetBrainsFork()
- actual val TvTimerProgramming: Key = implementedInJetBrainsFork()
- actual val StemPrimary: Key = implementedInJetBrainsFork()
- actual val Stem1: Key = implementedInJetBrainsFork()
- actual val Stem2: Key = implementedInJetBrainsFork()
- actual val Stem3: Key = implementedInJetBrainsFork()
- actual val AllApps: Key = implementedInJetBrainsFork()
- actual val Refresh: Key = implementedInJetBrainsFork()
- actual val ThumbsUp: Key = implementedInJetBrainsFork()
- actual val ThumbsDown: Key = implementedInJetBrainsFork()
- actual val ProfileSwitch: Key = implementedInJetBrainsFork()
- }
-}
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/key/KeyEvent.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/key/KeyEvent.linuxx64Stubs.kt
deleted file mode 100644
index 60d7851..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/key/KeyEvent.linuxx64Stubs.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2020 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.ui.input.key
-
-import androidx.compose.ui.implementedInJetBrainsFork
-
-actual class NativeKeyEvent
-
-actual val KeyEvent.key: Key
- get() = implementedInJetBrainsFork()
-
-actual val KeyEvent.utf16CodePoint: Int
- get() = implementedInJetBrainsFork()
-
-actual val KeyEvent.type: KeyEventType
- get() = implementedInJetBrainsFork()
-
-actual val KeyEvent.isAltPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val KeyEvent.isCtrlPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val KeyEvent.isMetaPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val KeyEvent.isShiftPressed: Boolean
- get() = implementedInJetBrainsFork()
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerEvent.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerEvent.linuxx64Stubs.kt
deleted file mode 100644
index 36e8bbc..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerEvent.linuxx64Stubs.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2020 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.ui.input.pointer
-
-import androidx.collection.LongSparseArray
-import androidx.compose.ui.implementedInJetBrainsFork
-
-internal actual class InternalPointerEvent
-actual constructor(
- actual val changes: LongSparseArray<PointerInputChange>,
- pointerInputEvent: PointerInputEvent
-) {
- actual var suppressMovementConsumption: Boolean = implementedInJetBrainsFork()
-
- actual fun activeHoverEvent(pointerId: PointerId): Boolean = implementedInJetBrainsFork()
-}
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.linuxx64Stubs.kt
deleted file mode 100644
index d0d1970..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.linuxx64Stubs.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2020 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.ui.input.pointer
-
-import androidx.compose.ui.implementedInJetBrainsFork
-
-internal actual typealias NativePointerButtons = Int
-
-internal actual typealias NativePointerKeyboardModifiers = Int
-
-internal actual fun EmptyPointerKeyboardModifiers(): PointerKeyboardModifiers =
- implementedInJetBrainsFork()
-
-actual data class PointerEvent
-internal actual constructor(
- actual val changes: List<PointerInputChange>,
- internal val internalPointerEvent: InternalPointerEvent?
-) {
- actual val buttons: PointerButtons
- get() = implementedInJetBrainsFork()
-
- actual val keyboardModifiers: PointerKeyboardModifiers
- get() = implementedInJetBrainsFork()
-
- actual var type: PointerEventType = implementedInJetBrainsFork()
-
- /** @param changes The changes. */
- actual constructor(changes: List<PointerInputChange>) : this(changes, null) {
- implementedInJetBrainsFork()
- }
-}
-
-actual val PointerButtons.isPrimaryPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerButtons.isSecondaryPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerButtons.isTertiaryPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerButtons.isBackPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerButtons.isForwardPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual fun PointerButtons.isPressed(buttonIndex: Int): Boolean = implementedInJetBrainsFork()
-
-actual val PointerButtons.areAnyPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual fun PointerButtons.indexOfFirstPressed(): Int = implementedInJetBrainsFork()
-
-actual fun PointerButtons.indexOfLastPressed(): Int = implementedInJetBrainsFork()
-
-actual val PointerKeyboardModifiers.isCtrlPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerKeyboardModifiers.isMetaPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerKeyboardModifiers.isAltPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerKeyboardModifiers.isAltGraphPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerKeyboardModifiers.isSymPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerKeyboardModifiers.isShiftPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerKeyboardModifiers.isFunctionPressed: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerKeyboardModifiers.isCapsLockOn: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerKeyboardModifiers.isScrollLockOn: Boolean
- get() = implementedInJetBrainsFork()
-
-actual val PointerKeyboardModifiers.isNumLockOn: Boolean
- get() = implementedInJetBrainsFork()
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.linuxx64Stubs.kt
deleted file mode 100644
index 5a75a35..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.linuxx64Stubs.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2021 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.ui.input.pointer
-
-import androidx.compose.ui.implementedInJetBrainsFork
-
-internal actual val pointerIconDefault: PointerIcon = implementedInJetBrainsFork()
-internal actual val pointerIconCrosshair: PointerIcon = implementedInJetBrainsFork()
-internal actual val pointerIconText: PointerIcon = implementedInJetBrainsFork()
-internal actual val pointerIconHand: PointerIcon = implementedInJetBrainsFork()
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.linuxx64Stubs.kt
deleted file mode 100644
index 61f79d8..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.linuxx64Stubs.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2020 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.ui.input.pointer
-
-internal actual data class PointerInputEvent(
- actual val uptime: Long,
- actual val pointers: List<PointerInputEventData>
-)
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.linuxx64Stubs.kt
deleted file mode 100644
index 5f0a650..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.linuxx64Stubs.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2023 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.ui.input.rotary
-
-actual class RotaryScrollEvent
-internal constructor(
- actual val verticalScrollPixels: Float,
- actual val horizontalScrollPixels: Float,
- actual val uptimeMillis: Long,
-)
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.linuxx64Stubs.kt
deleted file mode 100644
index beb813d..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.linuxx64Stubs.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2020 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.ui.platform
-
-import androidx.compose.ui.implementedInJetBrainsFork
-
-actual class ClipEntry {
- actual val clipMetadata: ClipMetadata
- get() = implementedInJetBrainsFork()
-}
-
-actual class ClipMetadata
-
-actual class NativeClipboard
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputMethodRequest.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputMethodRequest.linuxx64Stubs.kt
deleted file mode 100644
index a38f2b0..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputMethodRequest.linuxx64Stubs.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2023 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.ui.platform
-
-actual interface PlatformTextInputMethodRequest
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputSession.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputSession.linuxx64Stubs.kt
deleted file mode 100644
index e5b8469..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/PlatformTextInputSession.linuxx64Stubs.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2023 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.ui.platform
-
-actual interface PlatformTextInputSession {
- actual suspend fun startInputMethod(request: PlatformTextInputMethodRequest): Nothing
-}
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/Wrapper.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/Wrapper.linuxx64Stubs.kt
deleted file mode 100644
index 5150de3..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/platform/Wrapper.linuxx64Stubs.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2024 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.ui.platform
-
-import androidx.compose.runtime.CompositionContext
-import androidx.compose.runtime.ReusableComposition
-import androidx.compose.ui.implementedInJetBrainsFork
-import androidx.compose.ui.node.LayoutNode
-
-internal actual fun createSubcomposition(
- container: LayoutNode,
- parent: CompositionContext
-): ReusableComposition = implementedInJetBrainsFork()
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/viewinterop/InteropView.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/viewinterop/InteropView.linuxx64Stubs.kt
deleted file mode 100644
index d06483a..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/viewinterop/InteropView.linuxx64Stubs.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2023 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.ui.viewinterop
-
-actual typealias InteropView = Any
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/viewinterop/InteropViewFactoryHolder.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/viewinterop/InteropViewFactoryHolder.linuxx64Stubs.kt
deleted file mode 100644
index f1c2de3..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/viewinterop/InteropViewFactoryHolder.linuxx64Stubs.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2023 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.ui.viewinterop
-
-import androidx.compose.runtime.ComposeNodeLifecycleCallback
-import androidx.compose.ui.InternalComposeUiApi
-import androidx.compose.ui.implementedInJetBrainsFork
-
-@InternalComposeUiApi
-internal actual class InteropViewFactoryHolder private constructor() :
- ComposeNodeLifecycleCallback {
- init {
- implementedInJetBrainsFork()
- }
-
- actual fun getInteropView(): InteropView? = implementedInJetBrainsFork()
-
- actual override fun onReuse(): Unit = implementedInJetBrainsFork()
-
- actual override fun onDeactivate(): Unit = implementedInJetBrainsFork()
-
- actual override fun onRelease(): Unit = implementedInJetBrainsFork()
-}
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/window/Dialog.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/window/Dialog.linuxx64Stubs.kt
deleted file mode 100644
index 4974c54..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/window/Dialog.linuxx64Stubs.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2024 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.ui.window
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Immutable
-import androidx.compose.ui.implementedInJetBrainsFork
-
-@Immutable
-actual class DialogProperties
-actual constructor(
- actual val dismissOnBackPress: Boolean,
- actual val dismissOnClickOutside: Boolean,
- actual val usePlatformDefaultWidth: Boolean
-)
-
-@Composable
-actual fun Dialog(
- onDismissRequest: () -> Unit,
- properties: DialogProperties,
- content: @Composable () -> Unit
-): Unit = implementedInJetBrainsFork()
diff --git a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/window/Popup.linuxx64Stubs.kt b/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/window/Popup.linuxx64Stubs.kt
deleted file mode 100644
index 00833b2..0000000
--- a/compose/ui/ui/src/linuxx64StubsMain/kotlin/androidx/compose/ui/window/Popup.linuxx64Stubs.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2024 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.ui.window
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Immutable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.implementedInJetBrainsFork
-import androidx.compose.ui.unit.IntOffset
-
-@Immutable
-actual class PopupProperties
-actual constructor(
- actual val focusable: Boolean,
- actual val dismissOnBackPress: Boolean,
- actual val dismissOnClickOutside: Boolean,
- actual val clippingEnabled: Boolean,
-)
-
-@Composable
-actual fun Popup(
- alignment: Alignment,
- offset: IntOffset,
- onDismissRequest: (() -> Unit)?,
- properties: PopupProperties,
- content: @Composable () -> Unit
-): Unit = implementedInJetBrainsFork()
-
-@Composable
-actual fun Popup(
- popupPositionProvider: PopupPositionProvider,
- onDismissRequest: (() -> Unit)?,
- properties: PopupProperties,
- content: @Composable () -> Unit
-): Unit = implementedInJetBrainsFork()
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
index 0110a0c..1641a18 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
@@ -51,6 +51,7 @@
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.job
import kotlinx.coroutines.withTimeout
@@ -291,7 +292,7 @@
onSetActive: suspend () -> Unit,
onSetInactive: suspend () -> Unit,
block: CallControlScope.() -> Unit
- ) {
+ ) = coroutineScope {
// Provide a default empty handler for onEvent
addCall(
callAttributes,
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallsManagerExtensions.kt b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallsManagerExtensions.kt
index 4e5680e..be40159 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallsManagerExtensions.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/extensions/CallsManagerExtensions.kt
@@ -31,9 +31,9 @@
import androidx.core.telecom.internal.ParticipantStateListenerRemote
import androidx.core.telecom.util.ExperimentalAppActions
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.onCompletion
@@ -126,12 +126,15 @@
onSetActive: suspend () -> Unit,
onSetInactive: suspend () -> Unit,
init: suspend ExtensionInitializationScope.() -> Unit
-) {
+) = coroutineScope {
Log.v(CallsManagerExtensions.LOG_TAG, "addCall: begin")
val eventFlow = MutableSharedFlow<CallEvent>()
val scope = ExtensionInitializationScope()
- var extensionJob: Job? = null
scope.init()
+ val extensionJob = launch {
+ Log.d(CallsManagerExtensions.LOG_TAG, "addCall: connecting extensions")
+ scope.collectEvents(this, eventFlow)
+ }
Log.v(CallsManagerExtensions.LOG_TAG, "addCall: init complete")
addCall(
callAttributes,
@@ -151,16 +154,12 @@
foundEvent?.let { eventFlow.emit(CallEvent(it, extras)) }
}
) {
- extensionJob = launch {
- Log.d(CallsManagerExtensions.LOG_TAG, "addCall: connecting extensions")
- scope.collectEvents(this, eventFlow)
- }
Log.i(CallsManagerExtensions.LOG_TAG, "addCall: invoking delegates")
scope.invokeDelegate(this)
}
// Ensure that when the call ends, we also cancel any ongoing coroutines/flows as part of
// extension work
- extensionJob?.cancelAndJoin()
+ extensionJob.cancelAndJoin()
}
/**
diff --git a/development/project-creator/compose-template/groupId/artifactId/build.gradle b/development/project-creator/compose-template/groupId/artifactId/build.gradle
index ce8f88d..a83706a 100644
--- a/development/project-creator/compose-template/groupId/artifactId/build.gradle
+++ b/development/project-creator/compose-template/groupId/artifactId/build.gradle
@@ -21,6 +21,7 @@
* Please use that script when creating a new project, rather than copying an existing project and
* modifying its settings.
*/
+
import androidx.build.LibraryType
import androidx.build.PlatformIdentifier
@@ -32,18 +33,17 @@
androidXMultiplatform {
android()
- desktop()
+ jvmStubs()
+ linuxX64Stubs()
defaultPlatform(PlatformIdentifier.ANDROID)
sourceSets {
commonMain {
dependencies {
- implementation(libs.kotlinStdlibCommon)
+ implementation(libs.kotlinStdlib)
}
}
- androidMain.dependencies {
- }
commonTest {
dependencies {
@@ -60,7 +60,6 @@
}
}
-
androidMain {
dependsOn(jvmMain)
dependencies {
@@ -68,13 +67,6 @@
}
}
- desktopMain {
- dependsOn(jvmMain)
- dependencies {
- implementation(libs.kotlinStdlib)
- }
- }
-
jvmTest {
dependsOn(commonTest)
dependencies {
@@ -91,10 +83,16 @@
}
}
- desktopTest {
- dependsOn(jvmTest)
- dependencies {
- }
+ commonStubsMain {
+ dependsOn(commonMain)
+ }
+
+ jvmStubsMain {
+ dependsOn(commonStubsMain)
+ }
+
+ linuxx64StubsMain {
+ dependsOn(commonStubsMain)
}
}
}
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 343dc5c..897a4ac 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -88,7 +88,7 @@
kmpDocs(project(":compose:material3:material3-window-size-class"))
kmpDocs(project(":compose:material:material"))
kmpDocs("androidx.compose.material:material-icons-core:1.7.0-beta01")
- samples("androidx.compose.material:material-icons-core-samples:1.7.0-alpha07")
+ samples("androidx.compose.material:material-icons-core-samples:1.7.0-beta01")
kmpDocs(project(":compose:material:material-ripple"))
docs(project(":compose:material:material-navigation"))
kmpDocs(project(":compose:runtime:runtime"))
diff --git a/libraryversions.toml b/libraryversions.toml
index ff0aa52..e632472 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -42,7 +42,7 @@
CORE_REMOTEVIEWS = "1.1.0-rc01"
CORE_ROLE = "1.2.0-alpha01"
CORE_SPLASHSCREEN = "1.2.0-alpha01"
-CORE_TELECOM = "1.0.0-alpha09"
+CORE_TELECOM = "1.0.0-alpha10"
CORE_UWB = "1.0.0-alpha08"
CREDENTIALS = "1.3.0-rc01"
CREDENTIALS_E2EE_QUARANTINE = "1.0.0-alpha02"
diff --git a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/FlowLiveData.kt b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/FlowLiveData.kt
index a0dc7aa..ba172dc 100644
--- a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/FlowLiveData.kt
+++ b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/FlowLiveData.kt
@@ -101,9 +101,8 @@
public fun <T> LiveData<T>.asFlow(): Flow<T> =
callbackFlow {
val observer = Observer<T> { trySend(it) }
- withContext(Dispatchers.Main.immediate) { observeForever(observer) }
-
try {
+ withContext(Dispatchers.Main.immediate) { observeForever(observer) }
awaitCancellation()
} finally {
withContext(Dispatchers.Main.immediate + NonCancellable) {
diff --git a/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/LiveDataAsFlowTest.kt b/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/LiveDataAsFlowTest.kt
index 8b01a15..a1c69ec 100644
--- a/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/LiveDataAsFlowTest.kt
+++ b/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/LiveDataAsFlowTest.kt
@@ -54,6 +54,45 @@
}
@Test
+ fun checkCancellationFromInitialValue() {
+ val ld = MutableLiveData<Int>()
+ ld.value = 1
+ val flow = ld.asFlow()
+ // check that flow creation didn't make livedata active
+ assertThat(ld.hasActiveObservers()).isFalse()
+ // Collect only a single value to get the initial value and cancel immediately
+ val job = testScope.launch { assertThat(flow.take(1).toList()).isEqualTo(listOf(1)) }
+ scopes.triggerAllActions()
+ mainScope.launch {
+ // This should never be received by the take(1)
+ ld.value = 2
+ }
+ scopes.triggerAllActions()
+ // Verify that the job completing removes the observer
+ assertThat(job.isCompleted).isTrue()
+ assertThat(ld.hasActiveObservers()).isFalse()
+ }
+
+ @Test
+ fun checkCancellationAfterJobCompletes() {
+ val ld = MutableLiveData<Int>()
+ ld.value = 1
+ val flow = ld.asFlow()
+ // check that flow creation didn't make livedata active
+ assertThat(ld.hasActiveObservers()).isFalse()
+ val job = testScope.launch { assertThat(flow.take(2).toList()).isEqualTo(listOf(1, 2)) }
+ scopes.triggerAllActions()
+ mainScope.launch {
+ // Receiving this should complete the job and remove the observer
+ ld.value = 2
+ }
+ scopes.triggerAllActions()
+ // Verify that the job completing removes the observer
+ assertThat(job.isCompleted).isTrue()
+ assertThat(ld.hasActiveObservers()).isFalse()
+ }
+
+ @Test
fun dispatchMultiple() {
val ld = MutableLiveData<Int>()
val collected = mutableListOf<Int>()
diff --git a/mediarouter/mediarouter/api/current.txt b/mediarouter/mediarouter/api/current.txt
index 453631b..dc395c4 100644
--- a/mediarouter/mediarouter/api/current.txt
+++ b/mediarouter/mediarouter/api/current.txt
@@ -324,12 +324,23 @@
public abstract class MediaRouteProviderService extends android.app.Service {
ctor public MediaRouteProviderService();
+ method public void addClientInfoListener(java.util.concurrent.Executor, androidx.core.util.Consumer<java.util.List<androidx.mediarouter.media.MediaRouteProviderService.ClientInfo!>!>);
method public androidx.mediarouter.media.MediaRouteProvider? getMediaRouteProvider();
method public android.os.IBinder? onBind(android.content.Intent);
method public abstract androidx.mediarouter.media.MediaRouteProvider? onCreateMediaRouteProvider();
+ method public void removeClientInfoListener(androidx.core.util.Consumer<java.util.List<androidx.mediarouter.media.MediaRouteProviderService.ClientInfo!>!>);
field public static final String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
}
+ public static final class MediaRouteProviderService.ClientInfo {
+ method public String getPackageName();
+ }
+
+ public static final class MediaRouteProviderService.ClientInfo.Builder {
+ ctor public MediaRouteProviderService.ClientInfo.Builder(String);
+ method public androidx.mediarouter.media.MediaRouteProviderService.ClientInfo build();
+ }
+
public final class MediaRouteSelector {
method public android.os.Bundle asBundle();
method public boolean contains(androidx.mediarouter.media.MediaRouteSelector);
diff --git a/mediarouter/mediarouter/api/restricted_current.txt b/mediarouter/mediarouter/api/restricted_current.txt
index 453631b..dc395c4 100644
--- a/mediarouter/mediarouter/api/restricted_current.txt
+++ b/mediarouter/mediarouter/api/restricted_current.txt
@@ -324,12 +324,23 @@
public abstract class MediaRouteProviderService extends android.app.Service {
ctor public MediaRouteProviderService();
+ method public void addClientInfoListener(java.util.concurrent.Executor, androidx.core.util.Consumer<java.util.List<androidx.mediarouter.media.MediaRouteProviderService.ClientInfo!>!>);
method public androidx.mediarouter.media.MediaRouteProvider? getMediaRouteProvider();
method public android.os.IBinder? onBind(android.content.Intent);
method public abstract androidx.mediarouter.media.MediaRouteProvider? onCreateMediaRouteProvider();
+ method public void removeClientInfoListener(androidx.core.util.Consumer<java.util.List<androidx.mediarouter.media.MediaRouteProviderService.ClientInfo!>!>);
field public static final String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
}
+ public static final class MediaRouteProviderService.ClientInfo {
+ method public String getPackageName();
+ }
+
+ public static final class MediaRouteProviderService.ClientInfo.Builder {
+ ctor public MediaRouteProviderService.ClientInfo.Builder(String);
+ method public androidx.mediarouter.media.MediaRouteProviderService.ClientInfo build();
+ }
+
public final class MediaRouteSelector {
method public android.os.Bundle asBundle();
method public boolean contains(androidx.mediarouter.media.MediaRouteSelector);
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouteProviderServiceTest.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouteProviderServiceTest.java
index 02aea2f..9fd3cc8 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouteProviderServiceTest.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouteProviderServiceTest.java
@@ -31,7 +31,9 @@
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
+import android.text.TextUtils;
+import androidx.mediarouter.media.MediaRouteProviderService.ClientInfo;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -44,8 +46,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
@@ -75,11 +79,16 @@
private static CountDownLatch sActiveScanCountDownLatch;
private static CountDownLatch sPassiveScanCountDownLatch;
+ private static CountDownLatch sClientInfoListenerAdditionCountDownLatch;
+ private static CountDownLatch sClientInfoListenerRemovalCountDownLatch;
private static MediaRouteDiscoveryRequest sLastDiscoveryRequest;
+ private static List<ClientInfo> sLatestClientInfo = new ArrayList<>();
@Before
public void setUp() throws Exception {
resetActiveAndPassiveScanCountDownLatches();
+ resetClientInfoListenerAdditionCountDownLatch(2);
+ resetClientInfoListenerRemovalCountDownLatch(1);
Context context = ApplicationProvider.getApplicationContext();
Intent intent =
new Intent(context, MediaRouteProviderServiceImpl.class)
@@ -92,6 +101,8 @@
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO).build();
registerClient(mReceiveMessenger1);
registerClient(mReceiveMessenger2);
+ assertTrue(sClientInfoListenerAdditionCountDownLatch.await(
+ TIME_OUT_MS, TimeUnit.MILLISECONDS));
}
@After
@@ -261,6 +272,31 @@
assertTrue(sPassiveScanCountDownLatch.await(1000 + TIME_OUT_MS, TimeUnit.MILLISECONDS));
}
+ @LargeTest
+ @Test
+ public void testRegisterClient_clientRecordListenerCalled() throws Exception {
+ resetClientInfoListenerAdditionCountDownLatch(1);
+ resetClientInfoListenerRemovalCountDownLatch(1);
+ int initialCount = sLatestClientInfo.size();
+ Messenger messenger = new Messenger(new Handler(Looper.getMainLooper()));
+ registerClient(messenger);
+
+ assertTrue(
+ sClientInfoListenerAdditionCountDownLatch.await(
+ TIME_OUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(1, sLatestClientInfo.size() - initialCount);
+ Context context = ApplicationProvider.getApplicationContext();
+ for (ClientInfo clientInfo : sLatestClientInfo) {
+ assertTrue(TextUtils.equals(
+ context.getPackageName(), clientInfo.getPackageName()));
+ }
+
+ unregisterClient(messenger);
+ assertTrue(
+ sClientInfoListenerRemovalCountDownLatch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(initialCount, sLatestClientInfo.size());
+ }
+
private void registerClient(Messenger receiveMessenger) throws Exception {
Message msg = Message.obtain();
msg.what = MediaRouteProviderProtocol.CLIENT_MSG_REGISTER;
@@ -292,6 +328,23 @@
/** Fake {@link MediaRouteProviderService} implementation. */
public static final class MediaRouteProviderServiceImpl extends MediaRouteProviderService {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ sLatestClientInfo.clear();
+ addClientInfoListener(Executors.newSingleThreadExecutor(), clients -> {
+ int previousSize = sLatestClientInfo.size();
+ int newSize = clients.size();
+ sLatestClientInfo = clients;
+ if (newSize > previousSize) {
+ sClientInfoListenerAdditionCountDownLatch.countDown();
+ } else if (previousSize > newSize) {
+ sClientInfoListenerRemovalCountDownLatch.countDown();
+ }
+ });
+ }
+
@Override
public MediaRouteProvider onCreateMediaRouteProvider() {
return new MediaRouteProviderImpl(this);
@@ -325,4 +378,12 @@
sActiveScanCountDownLatch = new CountDownLatch(1);
sPassiveScanCountDownLatch = new CountDownLatch(1);
}
+
+ private void resetClientInfoListenerAdditionCountDownLatch(int count) {
+ sClientInfoListenerAdditionCountDownLatch = new CountDownLatch(count);
+ }
+
+ private void resetClientInfoListenerRemovalCountDownLatch(int count) {
+ sClientInfoListenerRemovalCountDownLatch = new CountDownLatch(count);
+ }
}
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderService.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderService.java
index 545cbf9..9520134 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderService.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderService.java
@@ -16,6 +16,7 @@
package androidx.mediarouter.media;
+import static androidx.mediarouter.media.MediaRouter.UNSELECT_REASON_UNKNOWN;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_MEMBER_ROUTE_ID;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_MEMBER_ROUTE_IDS;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_ID;
@@ -54,7 +55,6 @@
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_REGISTERED;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.SERVICE_VERSION_CURRENT;
import static androidx.mediarouter.media.MediaRouteProviderProtocol.isValidRemoteMessenger;
-import static androidx.mediarouter.media.MediaRouter.UNSELECT_REASON_UNKNOWN;
import android.app.Service;
import android.content.Context;
@@ -79,6 +79,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.collection.ArrayMap;
import androidx.core.content.ContextCompat;
+import androidx.core.util.Consumer;
import androidx.core.util.ObjectsCompat;
import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController;
import androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor;
@@ -89,8 +90,10 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Base class for media route provider services.
@@ -174,6 +177,9 @@
boolean onSetDiscoveryRequest(Messenger messenger, int requestId,
MediaRouteDiscoveryRequest request);
MediaRouteProvider.Callback getProviderCallback();
+ void addClientInfoListener(Executor listenerExecutor, Consumer<List<ClientInfo>> listener);
+ void removeClientInfoListener(Consumer<List<ClientInfo>> listener);
+ void removeAllClientInfoListeners();
}
/**
@@ -193,6 +199,31 @@
}
/**
+ * Adds a {@link Consumer} that will be used to provide updates when the list of
+ * bound clients changes.
+ *
+ * <p>The {@link Consumer} will be called with the current list of bound clients.</p>
+ *
+ * @param listenerExecutor an {@link Executor} that will be used to dispatch the callback
+ * @param listener a @code{@link Consumer} that takes a list of {@link ClientInfo}
+ */
+ public void addClientInfoListener(
+ @NonNull /* @CallbackExecutor */ Executor listenerExecutor,
+ @NonNull Consumer<List<ClientInfo>> listener) {
+ mImpl.addClientInfoListener(listenerExecutor, listener);
+ }
+
+ /**
+ * Removes the given {@link Consumer} if it was previously set.
+ *
+ * @param listener the {@link Consumer} to remove.
+ * @see #addClientInfoListener(Executor, Consumer)
+ */
+ public void removeClientInfoListener(@NonNull Consumer<List<ClientInfo>> listener) {
+ mImpl.removeClientInfoListener(listener);
+ }
+
+ /**
* Called by the system when it is time to create the media route provider.
*
* @return The media route provider offered by this service, or null if
@@ -250,6 +281,7 @@
if (mProvider != null) {
mProvider.setCallback(null);
}
+ mImpl.removeAllClientInfoListeners();
super.onDestroy();
}
@@ -309,6 +341,42 @@
return "Client connection " + messenger.getBinder().toString();
}
+ /**
+ * Contains information about a client that is bound to this service.
+ */
+ public static final class ClientInfo {
+ private final String packageName;
+
+ private ClientInfo(@NonNull String packageName) {
+ this.packageName = packageName;
+ }
+
+ /**
+ * Returns the package name of the client.
+ *
+ * @return The package name of the client
+ */
+ @NonNull
+ public String getPackageName() {
+ return packageName;
+ }
+
+ /** Builder for {@link ClientInfo}. */
+ public static final class Builder {
+ private final String packageName;
+
+ public Builder(@NonNull String packageName) {
+ this.packageName = packageName;
+ }
+
+ /** Builds and returns the {@link ClientInfo} object. */
+ @NonNull
+ public ClientInfo build() {
+ return new ClientInfo(packageName);
+ }
+ }
+ }
+
private final class PrivateHandler extends Handler {
PrivateHandler() {
}
@@ -490,10 +558,14 @@
static class MediaRouteProviderServiceImplBase implements MediaRouteProviderServiceImpl {
final MediaRouteProviderService mService;
- final ArrayList<ClientRecord> mClients = new ArrayList<ClientRecord>();
+ final ArrayList<ClientRecord> mClients = new ArrayList<>();
MediaRouteDiscoveryRequest mCompositeDiscoveryRequest;
MediaRouteDiscoveryRequest mBaseDiscoveryRequest;
long mBaseDiscoveryRequestTimestamp;
+ @Nullable
+ private final Map<Consumer<List<ClientInfo>>, Executor> mClientInfoListeners =
+ new HashMap<>();
+ private final Object mClientInfoListenersLock = new Object();
private final MediaRouterActiveScanThrottlingHelper mActiveScanThrottlingHelper =
new MediaRouterActiveScanThrottlingHelper(new Runnable() {
@Override
@@ -537,7 +609,7 @@
if (index < 0) {
ClientRecord client = createClientRecord(messenger, version, packageName);
if (client.register()) {
- mClients.add(client);
+ addClient(client);
if (DEBUG) {
Log.d(TAG, client + ": Registered, version=" + version);
}
@@ -560,7 +632,7 @@
public boolean onUnregisterClient(Messenger messenger, int requestId) {
int index = findClient(messenger);
if (index >= 0) {
- ClientRecord client = mClients.remove(index);
+ ClientRecord client = removeClient(index);
if (DEBUG) {
Log.d(TAG, client + ": Unregistered");
}
@@ -575,7 +647,7 @@
public void onBinderDied(Messenger messenger) {
int index = findClient(messenger);
if (index >= 0) {
- ClientRecord client = mClients.remove(index);
+ ClientRecord client = removeClient(index);
if (DEBUG) {
Log.d(TAG, client + ": Binder died");
}
@@ -1100,12 +1172,72 @@
sendMessage(mMessenger, SERVICE_MSG_DYNAMIC_ROUTE_DESCRIPTORS_CHANGED,
0, controllerId, bundle, null);
}
+
+ private ClientInfo getClientInfo() {
+ return new ClientInfo.Builder(mPackageName).build();
+ }
}
ClientRecord createClientRecord(Messenger messenger, int version, String packageName) {
return new ClientRecord(messenger, version, packageName);
}
+ private void addClient(ClientRecord client) {
+ mClients.add(client);
+ notifyClientRecordsChanged();
+ }
+
+ private ClientRecord removeClient(int index) {
+ ClientRecord removedClient = mClients.remove(index);
+ notifyClientRecordsChanged();
+ return removedClient;
+ }
+
+ @Override
+ public void addClientInfoListener (
+ @NonNull /* @CallbackExecutor */ Executor listenerExecutor,
+ @NonNull Consumer<List<ClientInfo>> listener) {
+ synchronized (mClientInfoListenersLock) {
+ mClientInfoListeners.put(listener, listenerExecutor);
+ // Immediately provide the current list of bound clients.
+ notifyClientRecordsChanged();
+ }
+ }
+
+ @Override
+ public void removeClientInfoListener(Consumer<List<ClientInfo>> listener) {
+ synchronized (mClientInfoListenersLock) {
+ mClientInfoListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public void removeAllClientInfoListeners() {
+ synchronized (mClientInfoListenersLock) {
+ mClientInfoListeners.clear();
+ }
+ }
+
+ private void notifyClientRecordsChanged() {
+ if (!mClientInfoListeners.isEmpty()) {
+ ArrayList<ClientInfo> clientInfos = new ArrayList<>();
+ for (ClientRecord client : new ArrayList<>(mClients)) {
+ clientInfos.add(client.getClientInfo());
+ }
+
+ synchronized (mClientInfoListenersLock) {
+ for (Map.Entry<Consumer<List<ClientInfo>>, Executor> entry :
+ mClientInfoListeners.entrySet()) {
+ Consumer<List<ClientInfo>> listener = entry.getKey();
+ Executor executor = entry.getValue();
+ executor.execute(() -> {
+ listener.accept(clientInfos);
+ });
+ }
+ }
+ }
+ }
+
class ProviderCallbackBase extends MediaRouteProvider.Callback {
@Override
public void onDescriptorChanged(@NonNull MediaRouteProvider provider,
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index 2e7e9c4..feafa4a 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -28,11 +28,11 @@
implementation(libs.kotlinStdlib)
api("androidx.activity:activity-compose:1.8.0")
- api("androidx.compose.animation:animation:1.7.0-beta04")
- implementation("androidx.compose.foundation:foundation-layout:1.7.0-beta04")
- api("androidx.compose.runtime:runtime:1.7.0-beta04")
- api("androidx.compose.runtime:runtime-saveable:1.7.0-beta04")
- api("androidx.compose.ui:ui:1.7.0-beta04")
+ api("androidx.compose.animation:animation:1.7.0-beta06")
+ implementation("androidx.compose.foundation:foundation-layout:1.7.0-beta06")
+ api("androidx.compose.runtime:runtime:1.7.0-beta06")
+ api("androidx.compose.runtime:runtime-saveable:1.7.0-beta06")
+ api("androidx.compose.ui:ui:1.7.0-beta06")
api("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
api(projectOrArtifact(":navigation:navigation-runtime-ktx"))
implementation(libs.kotlinSerializationCore)
diff --git a/privacysandbox/ads/OWNERS b/privacysandbox/ads/OWNERS
index dfb8b61..49fbef6 100644
--- a/privacysandbox/ads/OWNERS
+++ b/privacysandbox/ads/OWNERS
@@ -4,10 +4,15 @@
[email protected]
# adservices.customaudience, adservices.adselection OWNERS
[email protected] # FLEDGE Primary PoC
[email protected]
-# adservices.topics, .adid, .appsetid, .common OWNERS
[email protected] # Primary PoC for Topics, Common
[email protected] # Creator
[email protected]
+# adservices.topics
[email protected] # Primary PoC for Topics
[email protected]
+# adservices.signals
[email protected] # Primary PoC for Signals
[email protected]
+# adservices.adid, .appsetid, .common OWNERS
[email protected] # # Primary PoC for Common
[email protected]
[email protected] # DevRel Jetpack Lead
[email protected] # DevRel TPM Lead
[email protected] # Common PoC
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
index a5d8e0f..e837d59 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/MultiInstanceInvalidationTest.kt
@@ -60,13 +60,13 @@
// Remember the current auto close callback, it should be the one installed by the
// invalidation tracker
- val trackerCallback = autoCloseHelper.autoCloser.onAutoCloseCallback!!
+ val trackerCallback = autoCloseHelper.autoCloser.autoCloseCallbackForTest!!
// Set a new callback, intercepting when DB is auto-closed
val latch = CountDownLatch(1)
autoCloseHelper.autoCloser.setAutoCloseCallback {
// Run the remember auto close callback
- trackerCallback.run()
+ trackerCallback.invoke()
// At this point in time InvalidationTracker's callback has run and unbind should have
// been invoked.
latch.countDown()
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt
index cbe11d7..85ecd88 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTest.kt
@@ -16,36 +16,37 @@
package androidx.room.support
-import android.annotation.SuppressLint
-import androidx.arch.core.executor.ArchTaskExecutor
-import androidx.arch.core.executor.testing.CountingTaskExecutorRule
import androidx.kruth.assertThat
+import androidx.kruth.assertWithMessage
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.FlakyTest
import androidx.test.filters.MediumTest
import androidx.testutils.assertThrows
import java.io.IOException
import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest
-public class AutoCloserTest {
+class AutoCloserTest {
- @get:Rule
- public val countingTaskExecutorRule: CountingTaskExecutorRule = CountingTaskExecutorRule()
+ companion object {
+ private const val DB_NAME = "test.db"
+ private const val TIMEOUT_AMOUNT = 1L
+ }
+
+ private val testDispatcher = TestCoroutineScheduler()
private lateinit var autoCloser: AutoCloser
-
+ private lateinit var testWatch: AutoCloserTestWatch
private lateinit var callback: Callback
private class Callback(var throwOnOpen: Boolean = false) : SupportSQLiteOpenHelper.Callback(1) {
@@ -61,7 +62,8 @@
}
@Before
- public fun setUp() {
+ fun setUp() {
+ testWatch = AutoCloserTestWatch(TIMEOUT_AMOUNT, testDispatcher)
callback = Callback()
val delegateOpenHelper =
@@ -71,28 +73,27 @@
ApplicationProvider.getApplicationContext()
)
.callback(callback)
- .name("name")
+ .name(DB_NAME)
.build()
)
- val autoCloseExecutor = ArchTaskExecutor.getIOThreadExecutor()
-
autoCloser =
- AutoCloser(1, TimeUnit.MILLISECONDS, autoCloseExecutor).also {
- it.init(delegateOpenHelper)
- it.setAutoCloseCallback {}
+ AutoCloser(TIMEOUT_AMOUNT, TimeUnit.MILLISECONDS, testWatch).apply {
+ initOpenHelper(delegateOpenHelper)
+ initCoroutineScope(TestScope(testDispatcher))
+ setAutoCloseCallback {}
}
}
@After
- public fun cleanUp() {
- assertThat(countingTaskExecutorRule.isIdle).isTrue()
+ fun cleanUp() {
+ testWatch.step()
+ // At the end of all tests we always expect to auto-close the database
+ assertWithMessage("Database was not closed").that(autoCloser.delegateDatabase).isNull()
}
- @SuppressLint("BanThreadSleep")
- @Ignore("b/283959848")
@Test
- public fun refCountsCounted() {
+ fun refCountsCounted() {
autoCloser.incrementCountAndEnsureDbIsOpen()
assertThat(autoCloser.refCountForTest).isEqualTo(1)
@@ -109,51 +110,28 @@
autoCloser.decrementCountAndScheduleClose()
assertThat(autoCloser.refCountForTest).isEqualTo(0)
-
- // TODO(rohitsat): remove these sleeps and add a hook in AutoCloser to confirm that the
- // scheduled tasks are done.
- Thread.sleep(5)
- countingTaskExecutorRule.drainTasks(10, TimeUnit.MILLISECONDS)
}
- @SuppressLint("BanThreadSleep")
- @Ignore // b/271325600
@Test
- public fun executeRefCountingFunctionPropagatesFailure() {
- assertThrows<IOException> {
- autoCloser.executeRefCountingFunction<Nothing> { throw IOException() }
- }
+ fun executeRefCountingFunctionPropagatesFailure() {
+ assertThrows<IOException> { autoCloser.executeRefCountingFunction { throw IOException() } }
assertThat(autoCloser.refCountForTest).isEqualTo(0)
-
- // TODO(rohitsat): remove these sleeps and add a hook in AutoCloser to confirm that the
- // scheduled tasks are done.
- Thread.sleep(5)
- countingTaskExecutorRule.drainTasks(10, TimeUnit.MILLISECONDS)
}
- @SuppressLint("BanThreadSleep")
@Test
- @FlakyTest(bugId = 182343970)
- public fun dbNotClosedWithRefCountIncremented() {
+ fun dbNotClosedWithRefCountIncremented() {
autoCloser.incrementCountAndEnsureDbIsOpen()
- Thread.sleep(10)
+ testWatch.step()
assertThat(autoCloser.delegateDatabase!!.isOpen).isTrue()
autoCloser.decrementCountAndScheduleClose()
-
- // TODO(rohitsat): remove these sleeps and add a hook in AutoCloser to confirm that the
- // scheduled tasks are done.
- Thread.sleep(10)
- assertThat(autoCloser.delegateDatabase).isNull()
}
- @SuppressLint("BanThreadSleep")
- @FlakyTest(bugId = 189775887)
@Test
- public fun getDelegatedDatabaseReturnsUnwrappedDatabase() {
+ fun getDelegatedDatabaseReturnsUnwrappedDatabase() {
assertThat(autoCloser.delegateDatabase).isNull()
val db = autoCloser.incrementCountAndEnsureDbIsOpen()
@@ -170,16 +148,10 @@
autoCloser.executeRefCountingFunction {
assertThat(autoCloser.refCountForTest).isEqualTo(1)
}
-
- // TODO(rohitsat): remove these sleeps and add a hook in AutoCloser to confirm that the
- // scheduled tasks are done.
- Thread.sleep(5)
- countingTaskExecutorRule.drainTasks(10, TimeUnit.MILLISECONDS)
}
- @SuppressLint("BanThreadSleep")
@Test
- public fun refCountStaysIncrementedWhenErrorIsEncountered() {
+ fun refCountStaysIncrementedWhenErrorIsEncountered() {
callback.throwOnOpen = true
assertThrows<IOException> { autoCloser.incrementCountAndEnsureDbIsOpen() }
@@ -187,15 +159,10 @@
autoCloser.decrementCountAndScheduleClose()
callback.throwOnOpen = false
-
- // TODO(rohitsat): remove these sleeps and add a hook in AutoCloser to confirm that the
- // scheduled tasks are done.
- Thread.sleep(5)
- countingTaskExecutorRule.drainTasks(10, TimeUnit.MILLISECONDS)
}
@Test
- public fun testDbCanBeManuallyClosed() {
+ fun testDbCanBeManuallyClosed() {
val db = autoCloser.incrementCountAndEnsureDbIsOpen()
assertThat(db.isOpen).isTrue()
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTestWatch.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTestWatch.kt
new file mode 100644
index 0000000..c245c83
--- /dev/null
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoCloserTestWatch.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 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.room.support
+
+import kotlinx.coroutines.test.TestCoroutineScheduler
+
+internal class AutoCloserTestWatch(
+ private val autoCloseTimeout: Long,
+ private val testDispatcher: TestCoroutineScheduler
+) : AutoCloser.Watch {
+
+ private var currentMillis: Long = 0
+
+ override fun getMillis(): Long {
+ return currentMillis
+ }
+
+ /**
+ * Advances the internal time by [autoCloseTimeout] amount and executes pending tasks in the
+ * [testDispatcher].
+ */
+ fun step() {
+ currentMillis += autoCloseTimeout
+ testDispatcher.advanceUntilIdle()
+ }
+}
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt
index 6eeb4d3..05b661f 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactoryTest.kt
@@ -18,46 +18,60 @@
import android.annotation.SuppressLint
import android.content.Context
-import android.os.Build
-import androidx.annotation.RequiresApi
+import androidx.kruth.assertWithMessage
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
-import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
-public class AutoClosingRoomOpenHelperFactoryTest {
- private val DB_NAME = "name"
+class AutoClosingRoomOpenHelperFactoryTest {
+
+ companion object {
+ private const val DB_NAME = "test.db"
+ private const val TIMEOUT_AMOUNT = 10L
+ }
+
+ private val testDispatcher = TestCoroutineScheduler()
+
+ private lateinit var autoCloser: AutoCloser
+ private lateinit var testWatch: AutoCloserTestWatch
+ private lateinit var autoClosingRoomOpenHelperFactory: AutoClosingRoomOpenHelperFactory
@Before
- public fun setUp() {
+ fun setUp() {
ApplicationProvider.getApplicationContext<Context>().deleteDatabase(DB_NAME)
+
+ testWatch = AutoCloserTestWatch(TIMEOUT_AMOUNT, testDispatcher)
+ autoCloser =
+ AutoCloser(TIMEOUT_AMOUNT, TimeUnit.MILLISECONDS, testWatch).apply {
+ initCoroutineScope(TestScope(testDispatcher))
+ setAutoCloseCallback {}
+ }
+ autoClosingRoomOpenHelperFactory =
+ AutoClosingRoomOpenHelperFactory(
+ delegate = FrameworkSQLiteOpenHelperFactory(),
+ autoCloser = autoCloser
+ )
}
- private fun getAutoClosingRoomOpenHelperFactory(
- timeoutMillis: Long = 10
- ): AutoClosingRoomOpenHelperFactory {
- val delegateOpenHelperFactory = FrameworkSQLiteOpenHelperFactory()
-
- return AutoClosingRoomOpenHelperFactory(
- delegateOpenHelperFactory,
- AutoCloser(timeoutMillis, TimeUnit.MILLISECONDS, Executors.newSingleThreadExecutor())
- .also { it.onAutoCloseCallback = Runnable {} }
- )
+ @After
+ fun cleanUp() {
+ testWatch.step()
+ // At the end of all tests we always expect to auto-close the database
+ assertWithMessage("Database was not closed").that(autoCloser.delegateDatabase).isNull()
}
- @SuppressLint("BanThreadSleep")
- @RequiresApi(Build.VERSION_CODES.N)
@Test
- public fun testCallbacksCalled() {
- val autoClosingRoomOpenHelperFactory = getAutoClosingRoomOpenHelperFactory()
-
+ fun testCallbacksCalled() {
val callbackCount = AtomicInteger()
val countingCallback =
@@ -96,7 +110,7 @@
// onConfigure + onCreate + onOpen
assertEquals(3, callbackCount.get())
- Thread.sleep(100)
+ testWatch.step()
autoClosingRoomOpenHelper.writableDatabase
@@ -105,28 +119,25 @@
assertEquals(5, callbackCount.get())
}
- @RequiresApi(Build.VERSION_CODES.N)
@Test
- public fun testDatabaseIsOpenForSlowCallbacks() {
- val autoClosingRoomOpenHelperFactory = getAutoClosingRoomOpenHelperFactory()
-
+ fun testDatabaseIsOpenForSlowCallbacks() {
val refCountCheckingCallback =
object : SupportSQLiteOpenHelper.Callback(1) {
@SuppressLint("BanThreadSleep")
override fun onCreate(db: SupportSQLiteDatabase) {
- Thread.sleep(100)
+ testWatch.step()
db.execSQL("create table user (idk int)")
}
@SuppressLint("BanThreadSleep")
override fun onConfigure(db: SupportSQLiteDatabase) {
- Thread.sleep(100)
+ testWatch.step()
db.setMaximumSize(100000)
}
@SuppressLint("BanThreadSleep")
override fun onOpen(db: SupportSQLiteDatabase) {
- Thread.sleep(100)
+ testWatch.step()
db.execSQL("select * from user")
}
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt
index bd1478e..f92b715 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/support/AutoClosingRoomOpenHelperTest.kt
@@ -16,28 +16,38 @@
package androidx.room.support
-import android.annotation.SuppressLint
import android.content.Context
-import android.database.Cursor
import android.database.sqlite.SQLiteException
-import android.os.Build
-import androidx.annotation.RequiresApi
import androidx.kruth.assertThat
+import androidx.kruth.assertWithMessage
+import androidx.room.util.useCursor
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
-import androidx.test.filters.FlakyTest
import androidx.testutils.assertThrows
import java.io.IOException
-import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
class AutoClosingRoomOpenHelperTest {
+ companion object {
+ private const val DB_NAME = "test.db"
+ private const val TIMEOUT_AMOUNT = 10L
+ }
+
+ private val testDispatcher = TestCoroutineScheduler()
+
+ private lateinit var autoCloser: AutoCloser
+ private lateinit var testWatch: AutoCloserTestWatch
+ private lateinit var callback: Callback
+ private lateinit var autoClosingRoomOpenHelper: AutoClosingRoomOpenHelper
+
private open class Callback(var throwOnOpen: Boolean = false) :
SupportSQLiteOpenHelper.Callback(1) {
override fun onCreate(db: SupportSQLiteDatabase) {}
@@ -53,14 +63,10 @@
@Before
fun setUp() {
- ApplicationProvider.getApplicationContext<Context>().deleteDatabase("name")
- }
+ ApplicationProvider.getApplicationContext<Context>().deleteDatabase(DB_NAME)
- private fun getAutoClosingRoomOpenHelper(
- timeoutMillis: Long = 10,
- callback: SupportSQLiteOpenHelper.Callback = Callback()
- ): AutoClosingRoomOpenHelper {
-
+ testWatch = AutoCloserTestWatch(TIMEOUT_AMOUNT, testDispatcher)
+ callback = Callback()
val delegateOpenHelper =
FrameworkSQLiteOpenHelperFactory()
.create(
@@ -68,26 +74,28 @@
ApplicationProvider.getApplicationContext()
)
.callback(callback)
- .name("name")
+ .name(DB_NAME)
.build()
)
-
- val autoCloseExecutor = Executors.newSingleThreadExecutor()
-
- return AutoClosingRoomOpenHelper(
- delegateOpenHelper,
- AutoCloser(timeoutMillis, TimeUnit.MILLISECONDS, autoCloseExecutor).apply {
- init(delegateOpenHelper)
+ autoCloser =
+ AutoCloser(TIMEOUT_AMOUNT, TimeUnit.MILLISECONDS, testWatch).apply {
+ initOpenHelper(delegateOpenHelper)
+ initCoroutineScope(TestScope(testDispatcher))
setAutoCloseCallback {}
}
- )
+ autoClosingRoomOpenHelper =
+ AutoClosingRoomOpenHelper(delegate = delegateOpenHelper, autoCloser = autoCloser)
}
- @RequiresApi(Build.VERSION_CODES.N)
+ @After
+ fun cleanUp() {
+ testWatch.step()
+ // At the end of all tests we always expect to auto-close the database
+ assertWithMessage("Database was not closed").that(autoCloser.delegateDatabase).isNull()
+ }
+
@Test
fun testQueryFailureDecrementsRefCount() {
- val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
-
assertThrows<SQLiteException> {
autoClosingRoomOpenHelper.writableDatabase.query("select * from nonexistanttable")
}
@@ -95,10 +103,8 @@
assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(0)
}
- @RequiresApi(Build.VERSION_CODES.N)
@Test
fun testCursorKeepsDbAlive() {
- val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
autoClosingRoomOpenHelper.writableDatabase.execSQL("create table user (idk int)")
val cursor = autoClosingRoomOpenHelper.writableDatabase.query("select * from user")
@@ -107,35 +113,26 @@
assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(0)
}
- @RequiresApi(Build.VERSION_CODES.N)
@Test
fun testTransactionKeepsDbAlive() {
- val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
autoClosingRoomOpenHelper.writableDatabase.beginTransaction()
assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(1)
autoClosingRoomOpenHelper.writableDatabase.endTransaction()
assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(0)
}
- @SuppressLint("BanThreadSleep")
- @RequiresApi(Build.VERSION_CODES.N)
@Test
fun enableWriteAheadLogging_onOpenHelper() {
- val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
-
autoClosingRoomOpenHelper.setWriteAheadLoggingEnabled(true)
assertThat(autoClosingRoomOpenHelper.writableDatabase.isWriteAheadLoggingEnabled).isTrue()
- Thread.sleep(100) // Let the db auto close...
+ testWatch.step()
assertThat(autoClosingRoomOpenHelper.writableDatabase.isWriteAheadLoggingEnabled).isTrue()
}
- @RequiresApi(Build.VERSION_CODES.N)
@Test
fun testEnableWriteAheadLogging_onSupportSqliteDatabase_throwsUnsupportedOperation() {
- val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
-
assertThrows<UnsupportedOperationException> {
autoClosingRoomOpenHelper.writableDatabase.enableWriteAheadLogging()
}
@@ -145,67 +142,27 @@
}
}
- @SuppressLint("BanThreadSleep")
- @RequiresApi(Build.VERSION_CODES.N)
- @FlakyTest(bugId = 190607416)
@Test
- fun testOnOpenCalledOnEachOpen() {
- val countingCallback =
- object : Callback() {
- var onCreateCalls = 0
- var onOpenCalls = 0
-
- override fun onCreate(db: SupportSQLiteDatabase) {
- onCreateCalls++
- }
-
- override fun onOpen(db: SupportSQLiteDatabase) {
- super.onOpen(db)
- onOpenCalls++
- }
- }
-
- val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper(callback = countingCallback)
-
- autoClosingRoomOpenHelper.writableDatabase
- assertThat(countingCallback.onOpenCalls).isEqualTo(1)
- assertThat(countingCallback.onCreateCalls).isEqualTo(1)
-
- Thread.sleep(20) // Database should auto-close here
- autoClosingRoomOpenHelper.writableDatabase
- assertThat(countingCallback.onOpenCalls).isEqualTo(2)
- assertThat(countingCallback.onCreateCalls).isEqualTo(1)
- }
-
- @SuppressLint("BanThreadSleep")
- @Ignore // b/266993269
- @RequiresApi(Build.VERSION_CODES.N)
- @Test
- fun testStatementReturnedByCompileStatement_doesntKeepDatabaseOpen() {
- val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
-
+ fun testStatementReturnedByCompileStatement_doesNotKeepDatabaseOpen() {
val db = autoClosingRoomOpenHelper.writableDatabase
db.execSQL("create table user (idk int)")
db.compileStatement("insert into users (idk) values (1)")
- Thread.sleep(20)
+ testWatch.step()
+
assertThat(db.isOpen).isFalse() // db should close
assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(0)
}
- @SuppressLint("BanThreadSleep")
- @RequiresApi(Build.VERSION_CODES.N)
@Test
fun testStatementReturnedByCompileStatement_reOpensDatabase() {
- val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
-
val db = autoClosingRoomOpenHelper.writableDatabase
db.execSQL("create table user (idk int)")
val statement = db.compileStatement("insert into user (idk) values (1)")
- Thread.sleep(20)
+ testWatch.step()
statement.executeInsert() // This should succeed
@@ -214,10 +171,8 @@
assertThat(autoClosingRoomOpenHelper.autoCloser.refCountForTest).isEqualTo(0)
}
- @RequiresApi(Build.VERSION_CODES.N)
@Test
fun testStatementReturnedByCompileStatement_worksWithBinds() {
- val autoClosingRoomOpenHelper = getAutoClosingRoomOpenHelper()
val db = autoClosingRoomOpenHelper.writableDatabase
db.execSQL("create table users (i int, d double, b blob, n int, s string)")
@@ -265,27 +220,19 @@
ApplicationProvider.getApplicationContext()
)
.callback(Callback())
- .name("name")
+ .name(DB_NAME)
.build()
)
- val autoCloseExecutor = Executors.newSingleThreadExecutor()
-
val autoClosing =
AutoClosingRoomOpenHelper(
delegateOpenHelper,
- AutoCloser(0, TimeUnit.MILLISECONDS, autoCloseExecutor)
+ AutoCloser(0, TimeUnit.MILLISECONDS).apply {
+ initCoroutineScope(TestScope(testDispatcher))
+ setAutoCloseCallback {}
+ }
)
assertThat(autoClosing.delegate).isSameInstanceAs(delegateOpenHelper)
}
-
- // Older API versions didn't have Cursor implement Closeable
- private inline fun Cursor.useCursor(block: (Cursor) -> Unit) {
- try {
- block(this)
- } finally {
- this.close()
- }
- }
}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
index 6d2d169..eec93dd 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
@@ -228,23 +228,6 @@
validateAutoMigrations(configuration)
validateTypeConverters(configuration)
- // Configure SQLiteCopyOpenHelper if it is available
- unwrapOpenHelper(
- clazz = PrePackagedCopyOpenHelper::class.java,
- openHelper = connectionManager.supportOpenHelper
- )
- ?.setDatabaseConfiguration(configuration)
-
- // Configure AutoClosingRoomOpenHelper if it is available
- unwrapOpenHelper(
- clazz = AutoClosingRoomOpenHelper::class.java,
- openHelper = connectionManager.supportOpenHelper
- )
- ?.let {
- autoCloser = it.autoCloser
- invalidationTracker.setAutoCloser(it.autoCloser)
- }
-
if (configuration.queryCoroutineContext != null) {
// For backwards compatibility with internals not converted to Coroutines, use the
// provided dispatcher as executor.
@@ -283,6 +266,24 @@
allowMainThreadQueries = configuration.allowMainThreadQueries
+ // Configure SQLiteCopyOpenHelper if it is available
+ unwrapOpenHelper(
+ clazz = PrePackagedCopyOpenHelper::class.java,
+ openHelper = connectionManager.supportOpenHelper
+ )
+ ?.setDatabaseConfiguration(configuration)
+
+ // Configure AutoClosingRoomOpenHelper if it is available
+ unwrapOpenHelper(
+ clazz = AutoClosingRoomOpenHelper::class.java,
+ openHelper = connectionManager.supportOpenHelper
+ )
+ ?.let {
+ autoCloser = it.autoCloser
+ it.autoCloser.initCoroutineScope(coroutineScope)
+ invalidationTracker.setAutoCloser(it.autoCloser)
+ }
+
// Configure multi-instance invalidation, if enabled
if (configuration.multiInstanceInvalidationServiceIntent != null) {
requireNotNull(configuration.name)
@@ -1606,11 +1607,7 @@
"Cannot create auto-closing database for an in-memory database."
}
val autoCloser =
- AutoCloser(
- autoCloseTimeout,
- requireNotNull(autoCloseTimeUnit),
- requireNotNull(queryExecutor)
- )
+ AutoCloser(autoCloseTimeout, requireNotNull(autoCloseTimeUnit))
AutoClosingRoomOpenHelperFactory(it, autoCloser)
} else {
it
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoCloser.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoCloser.android.kt
index a3143e6..ac86ceb 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoCloser.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoCloser.android.kt
@@ -15,76 +15,76 @@
*/
package androidx.room.support
-import android.os.Handler
-import android.os.Looper
import android.os.SystemClock
import androidx.annotation.GuardedBy
+import androidx.room.support.AutoCloser.Watch
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
-import java.io.IOException
-import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
/**
- * AutoCloser is responsible for automatically opening (using delegateOpenHelper) and closing (on a
- * timer started when there are no remaining references) a SupportSqliteDatabase.
+ * AutoCloser is responsible for automatically opening (using `delegateOpenHelper`) and closing (on
+ * a timer started when there are no remaining references) a [SupportSQLiteDatabase].
*
- * It is important to ensure that the ref count is incremented when using a returned database.
+ * It is important to ensure that the reference count is incremented when using a returned database.
*
- * @param autoCloseTimeoutAmount time for auto close timer
- * @param autoCloseTimeUnit time unit for autoCloseTimeoutAmount
- * @param autoCloseExecutor the executor on which the auto close operation will happen
+ * @param timeoutAmount time for auto close timer
+ * @param timeUnit time unit for `timeoutAmount`
+ * @param watch A [Watch] implementation to get an increasing timestamp.
*/
internal class AutoCloser(
- autoCloseTimeoutAmount: Long,
- autoCloseTimeUnit: TimeUnit,
- autoCloseExecutor: Executor
+ timeoutAmount: Long,
+ timeUnit: TimeUnit,
+ private val watch: Watch = Watch { SystemClock.uptimeMillis() }
) {
- lateinit var delegateOpenHelper: SupportSQLiteOpenHelper
- private val handler = Handler(Looper.getMainLooper())
+ // The unwrapped SupportSQLiteOpenHelper (i.e. not AutoClosingRoomOpenHelper)
+ private lateinit var delegateOpenHelper: SupportSQLiteOpenHelper
- internal var onAutoCloseCallback: Runnable? = null
+ private lateinit var coroutineScope: CoroutineScope
+
+ private var onAutoCloseCallback: (() -> Unit)? = null
private val lock = Any()
- private var autoCloseTimeoutInMs: Long = autoCloseTimeUnit.toMillis(autoCloseTimeoutAmount)
+ private val autoCloseTimeoutInMs = timeUnit.toMillis(timeoutAmount)
- private val executor: Executor = autoCloseExecutor
+ private val referenceCount = AtomicInteger(0)
- @GuardedBy("lock") internal var refCount = 0
+ private var lastDecrementRefCountTimeStamp = AtomicLong(watch.getMillis())
- @GuardedBy("lock") internal var lastDecrementRefCountTimeStamp = SystemClock.uptimeMillis()
-
- // The unwrapped SupportSqliteDatabase
+ // The unwrapped SupportSqliteDatabase (i.e. not AutoCloseSupportSQLiteDatabase)
@GuardedBy("lock") internal var delegateDatabase: SupportSQLiteDatabase? = null
private var manuallyClosed = false
- private val executeAutoCloser = Runnable { executor.execute(autoCloser) }
+ private var autoCloseJob: Job? = null
- private val autoCloser = Runnable {
+ private fun autoCloseDatabase() {
+ if (watch.getMillis() - lastDecrementRefCountTimeStamp.get() < autoCloseTimeoutInMs) {
+ // An increment + decrement beat us to closing the db. We
+ // will not close the database, and there should be at least
+ // one more auto-close scheduled.
+ return
+ }
+ if (referenceCount.get() != 0) {
+ // An increment beat us to closing the db. We don't close the
+ // db, and another closer will be scheduled once the ref
+ // count is decremented.
+ return
+ }
+ onAutoCloseCallback?.invoke()
+ ?: error(
+ "onAutoCloseCallback is null but it should have been set before use. " +
+ "Please file a bug against Room at: $BUG_LINK"
+ )
+
synchronized(lock) {
- if (
- SystemClock.uptimeMillis() - lastDecrementRefCountTimeStamp < autoCloseTimeoutInMs
- ) {
- // An increment + decrement beat us to closing the db. We
- // will not close the database, and there should be at least
- // one more auto-close scheduled.
- return@Runnable
- }
- if (refCount != 0) {
- // An increment beat us to closing the db. We don't close the
- // db, and another closer will be scheduled once the ref
- // count is decremented.
- return@Runnable
- }
- onAutoCloseCallback?.run()
- ?: error(
- "onAutoCloseCallback is null but it should" +
- " have been set before use. Please file a bug " +
- "against Room at: $autoCloseBug"
- )
-
delegateDatabase?.let {
if (it.isOpen) {
it.close()
@@ -95,16 +95,27 @@
}
/**
- * Since we need to construct the AutoCloser in the RoomDatabase.Builder, we need to set the
- * delegateOpenHelper after construction.
+ * Since we need to construct the AutoCloser in the [androidx.room.RoomDatabase.Builder], we
+ * need to set the `delegateOpenHelper` after construction.
*
- * @param delegateOpenHelper the open helper that is used to create new SupportSqliteDatabases
+ * @param delegateOpenHelper the open helper that is used to create new [SupportSQLiteDatabase].
*/
- fun init(delegateOpenHelper: SupportSQLiteOpenHelper) {
+ fun initOpenHelper(delegateOpenHelper: SupportSQLiteOpenHelper) {
+ require(delegateOpenHelper !is AutoClosingRoomOpenHelper)
this.delegateOpenHelper = delegateOpenHelper
}
/**
+ * Since we need to construct the AutoCloser in the [androidx.room.RoomDatabase.Builder], we
+ * need to set the `coroutineScope` after construction.
+ *
+ * @param coroutineScope where the auto close will execute.
+ */
+ fun initCoroutineScope(coroutineScope: CoroutineScope) {
+ this.coroutineScope = coroutineScope
+ }
+
+ /**
* Execute a ref counting function. The function will receive an unwrapped open database and
* this database will stay open until at least after function returns. If there are no more
* references in use for the db once function completes, an auto close operation will be
@@ -118,25 +129,25 @@
}
/**
- * Confirms that autoCloser is no longer running and confirms that delegateDatabase is set and
- * open. delegateDatabase will not be auto closed until decrementRefCountAndScheduleClose is
- * called. decrementRefCountAndScheduleClose must be called once for each call to
- * incrementCountAndEnsureDbIsOpen.
+ * Confirms that auto-close function is no longer running and confirms that `delegateDatabase`
+ * is set and open. `delegateDatabase` will not be auto closed until
+ * [decrementCountAndScheduleClose] is called. [decrementCountAndScheduleClose] must be called
+ * once for each call to [incrementCountAndEnsureDbIsOpen].
*
- * If this throws an exception, decrementCountAndScheduleClose must still be called!
+ * If this throws an exception, [decrementCountAndScheduleClose] must still be called!
*
* @return the *unwrapped* SupportSQLiteDatabase.
*/
fun incrementCountAndEnsureDbIsOpen(): SupportSQLiteDatabase {
- // TODO(rohitsat): avoid synchronized(lock) when possible. We should be able to avoid it
- // when refCount is not hitting zero or if there is no auto close scheduled if we use
- // Atomics.
- synchronized(lock) {
+ val previousCount = referenceCount.getAndIncrement()
- // If there is a scheduled autoclose operation, we should remove it from the handler.
- handler.removeCallbacks(executeAutoCloser)
- refCount++
- check(!manuallyClosed) { "Attempting to open already closed database." }
+ // If there is a scheduled auto close operation, cancel it.
+ autoCloseJob?.cancel()
+ autoCloseJob = null
+
+ check(!manuallyClosed) { "Attempting to open already closed database." }
+
+ fun getDatabase(): SupportSQLiteDatabase {
delegateDatabase?.let {
if (it.isOpen) {
return it
@@ -144,39 +155,41 @@
}
return delegateOpenHelper.writableDatabase.also { delegateDatabase = it }
}
+
+ // Fast path: If the previous count was not zero, then there is no concurrent auto close
+ // operation that can race with getting the database, so we skip using the lock.
+ if (previousCount > 0) {
+ return getDatabase()
+ }
+ // Slow path: If the previous count was indeed zero, even though we cancel the auto close
+ // operation, it might be on-going already so to avoid a race we use the lock to get the
+ // database.
+ return synchronized(lock) { getDatabase() }
}
/**
* Decrements the ref count and schedules a close if there are no other references to the db.
- * This must only be called after a corresponding incrementCountAndEnsureDbIsOpen call.
+ * This must only be called after a corresponding [incrementCountAndEnsureDbIsOpen] call.
*/
fun decrementCountAndScheduleClose() {
- // TODO(rohitsat): avoid synchronized(lock) when possible
- synchronized(lock) {
- check(refCount > 0) { "ref count is 0 or lower but we're supposed to decrement" }
- // decrement refCount
- refCount--
-
- // if refcount is zero, schedule close operation
- if (refCount == 0) {
- if (delegateDatabase == null) {
- // No db to close, this can happen due to exceptions when creating db...
- return
+ val newCount = referenceCount.decrementAndGet()
+ check(newCount >= 0) { "Unbalanced reference count." }
+ lastDecrementRefCountTimeStamp.set(watch.getMillis())
+ if (newCount == 0) {
+ autoCloseJob =
+ coroutineScope.launch {
+ delay(autoCloseTimeoutInMs)
+ autoCloseDatabase()
}
- handler.postDelayed(executeAutoCloser, autoCloseTimeoutInMs)
- }
}
}
- /**
- * Close the database if it is still active.
- *
- * @throws IOException if an exception is encountered when closing the underlying db.
- */
- @Throws(IOException::class)
+ /** Close the database if it is still active. */
fun closeDatabaseIfOpen() {
synchronized(lock) {
manuallyClosed = true
+ autoCloseJob?.cancel()
+ autoCloseJob = null
delegateDatabase?.close()
delegateDatabase = null
}
@@ -192,29 +205,30 @@
get() = !manuallyClosed
/**
- * Returns the current ref count for this auto closer. This is only visible for testing.
- *
- * @return current ref count
- */
- internal val refCountForTest: Int
- get() {
- synchronized(lock) {
- return refCount
- }
- }
-
- /**
* Sets a callback that will be run every time the database is auto-closed. This callback needs
* to be lightweight since it is run while holding a lock.
*
* @param onAutoClose the callback to run
*/
- fun setAutoCloseCallback(onAutoClose: Runnable) {
+ fun setAutoCloseCallback(onAutoClose: () -> Unit) {
onAutoCloseCallback = onAutoClose
}
+ /** Returns the current auto close callback. This is only visible for testing. */
+ internal val autoCloseCallbackForTest
+ get() = onAutoCloseCallback
+
+ /** Returns the current ref count for this auto closer. This is only visible for testing. */
+ internal val refCountForTest: Int
+ get() = referenceCount.get()
+
+ /** Represents a counting time tracker function. */
+ fun interface Watch {
+ fun getMillis(): Long
+ }
+
companion object {
- const val autoCloseBug =
- "https://issuetracker.google.com/issues/new?component=" + "413107&template=1096568"
+ const val BUG_LINK =
+ "https://issuetracker.google.com/issues/new?component=413107&template=1096568"
}
}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt
index 2bb22cf..a5dc11d 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelper.android.kt
@@ -15,21 +15,18 @@
*/
package androidx.room.support
-import android.content.ContentResolver
import android.content.ContentValues
import android.database.Cursor
import android.database.SQLException
import android.database.sqlite.SQLiteTransactionListener
-import android.net.Uri
import android.os.Build
-import android.os.Bundle
import android.os.CancellationSignal
import android.util.Pair
import androidx.annotation.RequiresApi
import androidx.room.DelegatingOpenHelper
-import androidx.sqlite.db.SupportSQLiteCompat
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
+import androidx.sqlite.db.SupportSQLiteProgram
import androidx.sqlite.db.SupportSQLiteQuery
import androidx.sqlite.db.SupportSQLiteStatement
import java.io.IOException
@@ -38,23 +35,21 @@
/** A SupportSQLiteOpenHelper that has auto close enabled for database connections. */
internal class AutoClosingRoomOpenHelper(
override val delegate: SupportSQLiteOpenHelper,
- @JvmField internal val autoCloser: AutoCloser
+ internal val autoCloser: AutoCloser
) : SupportSQLiteOpenHelper by delegate, DelegatingOpenHelper {
- private val autoClosingDb: AutoClosingSupportSQLiteDatabase
+
+ private val autoClosingDb = AutoClosingSupportSQLiteDatabase(autoCloser)
init {
- autoCloser.init(delegate)
- autoClosingDb = AutoClosingSupportSQLiteDatabase(autoCloser)
+ autoCloser.initOpenHelper(delegate)
}
- @get:RequiresApi(api = Build.VERSION_CODES.N)
override val writableDatabase: SupportSQLiteDatabase
get() {
autoClosingDb.pokeOpen()
return autoClosingDb
}
- @get:RequiresApi(api = Build.VERSION_CODES.N)
override val readableDatabase: SupportSQLiteDatabase
get() {
// Note we don't differentiate between writable db and readable db
@@ -75,7 +70,7 @@
}
override fun compileStatement(sql: String): SupportSQLiteStatement {
- return AutoClosingSupportSqliteStatement(sql, autoCloser)
+ return AutoClosingSupportSQLiteStatement(sql, autoCloser)
}
override fun beginTransaction() {
@@ -138,9 +133,6 @@
}
override fun endTransaction() {
- checkNotNull(autoCloser.delegateDatabase) {
- "End transaction called but delegateDb is null"
- }
try {
autoCloser.delegateDatabase!!.endTransaction()
} finally {
@@ -149,8 +141,7 @@
}
override fun setTransactionSuccessful() {
- autoCloser.delegateDatabase?.setTransactionSuccessful()
- ?: error("setTransactionSuccessful called but delegateDb is null")
+ autoCloser.delegateDatabase!!.setTransactionSuccessful()
}
override fun inTransaction(): Boolean {
@@ -186,9 +177,8 @@
override var version: Int
get() = autoCloser.executeRefCountingFunction(SupportSQLiteDatabase::version)
set(version) {
- autoCloser.executeRefCountingFunction<Any?> { db: SupportSQLiteDatabase ->
+ autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase ->
db.version = version
- null
}
}
@@ -285,32 +275,24 @@
@Throws(SQLException::class)
override fun execSQL(sql: String) {
- autoCloser.executeRefCountingFunction<Any?> { db: SupportSQLiteDatabase ->
- db.execSQL(sql)
- null
- }
+ autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase -> db.execSQL(sql) }
}
@Throws(SQLException::class)
override fun execSQL(sql: String, bindArgs: Array<out Any?>) {
- autoCloser.executeRefCountingFunction<Any?> { db: SupportSQLiteDatabase ->
+ autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase ->
db.execSQL(sql, bindArgs)
- null
}
}
override val isReadOnly: Boolean
- get() =
- autoCloser.executeRefCountingFunction { obj: SupportSQLiteDatabase ->
- obj.isReadOnly
- }
+ get() = autoCloser.executeRefCountingFunction(SupportSQLiteDatabase::isReadOnly)
override val isOpen: Boolean
get() {
// Get the db without incrementing the reference cause we don't want to open
// the db for an isOpen call.
- val localDelegate = autoCloser.delegateDatabase ?: return false
- return localDelegate.isOpen
+ return autoCloser.delegateDatabase?.isOpen ?: return false
}
override fun needUpgrade(newVersion: Int): Boolean {
@@ -320,26 +302,23 @@
}
override val path: String?
- get() = autoCloser.executeRefCountingFunction { obj: SupportSQLiteDatabase -> obj.path }
+ get() = autoCloser.executeRefCountingFunction(SupportSQLiteDatabase::path)
override fun setLocale(locale: Locale) {
- autoCloser.executeRefCountingFunction<Any?> { db: SupportSQLiteDatabase ->
+ autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase ->
db.setLocale(locale)
- null
}
}
override fun setMaxSqlCacheSize(cacheSize: Int) {
- autoCloser.executeRefCountingFunction<Any?> { db: SupportSQLiteDatabase ->
+ autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase ->
db.setMaxSqlCacheSize(cacheSize)
- null
}
}
override fun setForeignKeyConstraintsEnabled(enabled: Boolean) {
- autoCloser.executeRefCountingFunction<Any?> { db: SupportSQLiteDatabase ->
+ autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase ->
db.setForeignKeyConstraintsEnabled(enabled)
- null
}
}
@@ -359,21 +338,16 @@
override val isWriteAheadLoggingEnabled: Boolean
get() =
- autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase ->
- return@executeRefCountingFunction db.isWriteAheadLoggingEnabled
- }
+ autoCloser.executeRefCountingFunction(
+ SupportSQLiteDatabase::isWriteAheadLoggingEnabled
+ )
override val attachedDbs: List<Pair<String, String>>?
- get() =
- autoCloser.executeRefCountingFunction { obj: SupportSQLiteDatabase ->
- obj.attachedDbs
- }
+ get() = autoCloser.executeRefCountingFunction(SupportSQLiteDatabase::attachedDbs)
override val isDatabaseIntegrityOk: Boolean
get() =
- autoCloser.executeRefCountingFunction { obj: SupportSQLiteDatabase ->
- obj.isDatabaseIntegrityOk
- }
+ autoCloser.executeRefCountingFunction(SupportSQLiteDatabase::isDatabaseIntegrityOk)
@Throws(IOException::class)
override fun close() {
@@ -395,150 +369,141 @@
delegate.close()
autoCloser.decrementCountAndScheduleClose()
}
-
- @RequiresApi(api = Build.VERSION_CODES.Q)
- override fun setNotificationUris(cr: ContentResolver, uris: List<Uri>) {
- SupportSQLiteCompat.Api29Impl.setNotificationUris(delegate, cr, uris)
- }
-
- override fun getNotificationUri(): Uri {
- return delegate.notificationUri
- }
-
- @RequiresApi(api = Build.VERSION_CODES.Q)
- override fun getNotificationUris(): List<Uri> {
- return SupportSQLiteCompat.Api29Impl.getNotificationUris(delegate)
- }
-
- @RequiresApi(api = Build.VERSION_CODES.M)
- override fun setExtras(extras: Bundle) {
- SupportSQLiteCompat.Api23Impl.setExtras(delegate, extras)
- }
}
/**
- * We can't close our db if the SupportSqliteStatement is open.
- *
- * Each of these that are created need to be registered with RefCounter.
- *
- * On auto-close, RefCounter needs to close each of these before closing the db that these were
- * constructed from.
- *
- * Each of the methods here need to get
+ * Since long-living statements are a normal use-case, auto-close does not have a keep-alive
+ * statement, instead records SQL query and binding args and replicates on execution, opening
+ * the database is necessary but not helding a ref count on it.
*/
- // TODO(rohitsat) cache the prepared statement... I'm not sure what the performance implications
- // are for the way it's done here, but caching the prepared statement would definitely be more
- // complicated since we need to invalidate any of the PreparedStatements that were created
- // with this db
- private class AutoClosingSupportSqliteStatement(
+ private class AutoClosingSupportSQLiteStatement(
private val sql: String,
private val autoCloser: AutoCloser
) : SupportSQLiteStatement {
- private val binds = ArrayList<Any?>()
- private fun <T> executeSqliteStatementWithRefCount(
- block: (SupportSQLiteStatement) -> T
- ): T {
- return autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase ->
- val statement: SupportSQLiteStatement = db.compileStatement(sql)
- doBinds(statement)
- block(statement)
- }
- }
+ private var bindingTypes: IntArray = IntArray(0)
+ private var longBindings: LongArray = LongArray(0)
+ private var doubleBindings: DoubleArray = DoubleArray(0)
+ private var stringBindings: Array<String?> = emptyArray()
+ private var blobBindings: Array<ByteArray?> = emptyArray()
- private fun doBinds(supportSQLiteStatement: SupportSQLiteStatement) {
- // Replay the binds
- binds.forEachIndexed { i, _ ->
- val bindIndex = i + 1 // Bind indices are 1 based so we start at 1 not 0
- when (val bind = binds[i]) {
- null -> {
- supportSQLiteStatement.bindNull(bindIndex)
- }
- is Long -> {
- supportSQLiteStatement.bindLong(bindIndex, bind)
- }
- is Double -> {
- supportSQLiteStatement.bindDouble(bindIndex, bind)
- }
- is String -> {
- supportSQLiteStatement.bindString(bindIndex, bind)
- }
- is ByteArray -> {
- supportSQLiteStatement.bindBlob(bindIndex, bind)
- }
- }
- }
- }
-
- private fun saveBinds(bindIndex: Int, value: Any?) {
- val index = bindIndex - 1
- if (index >= binds.size) {
- // Add null entries to the list until we have the desired # of indices
- for (i in binds.size..index) {
- binds.add(null)
- }
- }
- binds[index] = value
- }
-
- @Throws(IOException::class)
override fun close() {
- // Nothing to do here since we re-compile the statement each time.
+ // Not much to do here since we re-compile the statement each time.
+ clearBindings()
}
override fun execute() {
- executeSqliteStatementWithRefCount<Any?> { statement: SupportSQLiteStatement ->
- statement.execute()
- null
- }
+ executeWithRefCount { statement: SupportSQLiteStatement -> statement.execute() }
}
override fun executeUpdateDelete(): Int {
- return executeSqliteStatementWithRefCount { obj: SupportSQLiteStatement ->
- obj.executeUpdateDelete()
- }
+ return executeWithRefCount { obj: SupportSQLiteStatement -> obj.executeUpdateDelete() }
}
override fun executeInsert(): Long {
- return executeSqliteStatementWithRefCount { obj: SupportSQLiteStatement ->
- obj.executeInsert()
- }
+ return executeWithRefCount { obj: SupportSQLiteStatement -> obj.executeInsert() }
}
override fun simpleQueryForLong(): Long {
- return executeSqliteStatementWithRefCount { obj: SupportSQLiteStatement ->
- obj.simpleQueryForLong()
- }
+ return executeWithRefCount { obj: SupportSQLiteStatement -> obj.simpleQueryForLong() }
}
override fun simpleQueryForString(): String? {
- return executeSqliteStatementWithRefCount { obj: SupportSQLiteStatement ->
- obj.simpleQueryForString()
+ return executeWithRefCount { obj: SupportSQLiteStatement -> obj.simpleQueryForString() }
+ }
+
+ private fun <T> executeWithRefCount(block: (SupportSQLiteStatement) -> T): T {
+ return autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase ->
+ val actualStatement = db.compileStatement(sql)
+ bindTo(actualStatement)
+ block(actualStatement)
}
}
override fun bindNull(index: Int) {
- saveBinds(index, null)
+ ensureCapacity(COLUMN_TYPE_NULL, index)
+ bindingTypes[index] = COLUMN_TYPE_NULL
}
override fun bindLong(index: Int, value: Long) {
- saveBinds(index, value)
+ ensureCapacity(COLUMN_TYPE_LONG, index)
+ bindingTypes[index] = COLUMN_TYPE_LONG
+ longBindings[index] = value
}
override fun bindDouble(index: Int, value: Double) {
- saveBinds(index, value)
+ ensureCapacity(COLUMN_TYPE_DOUBLE, index)
+ bindingTypes[index] = COLUMN_TYPE_DOUBLE
+ doubleBindings[index] = value
}
override fun bindString(index: Int, value: String) {
- saveBinds(index, value)
+ ensureCapacity(COLUMN_TYPE_STRING, index)
+ bindingTypes[index] = COLUMN_TYPE_STRING
+ stringBindings[index] = value
}
override fun bindBlob(index: Int, value: ByteArray) {
- saveBinds(index, value)
+ ensureCapacity(COLUMN_TYPE_BLOB, index)
+ bindingTypes[index] = COLUMN_TYPE_BLOB
+ blobBindings[index] = value
}
override fun clearBindings() {
- binds.clear()
+ bindingTypes = IntArray(0)
+ longBindings = LongArray(0)
+ doubleBindings = DoubleArray(0)
+ stringBindings = emptyArray()
+ blobBindings = emptyArray()
+ }
+
+ private fun ensureCapacity(columnType: Int, index: Int) {
+ val requiredSize = index + 1
+ if (bindingTypes.size < requiredSize) {
+ bindingTypes = bindingTypes.copyOf(requiredSize)
+ }
+ when (columnType) {
+ COLUMN_TYPE_LONG -> {
+ if (longBindings.size < requiredSize) {
+ longBindings = longBindings.copyOf(requiredSize)
+ }
+ }
+ COLUMN_TYPE_DOUBLE -> {
+ if (doubleBindings.size < requiredSize) {
+ doubleBindings = doubleBindings.copyOf(requiredSize)
+ }
+ }
+ COLUMN_TYPE_STRING -> {
+ if (stringBindings.size < requiredSize) {
+ stringBindings = stringBindings.copyOf(requiredSize)
+ }
+ }
+ COLUMN_TYPE_BLOB -> {
+ if (blobBindings.size < requiredSize) {
+ blobBindings = blobBindings.copyOf(requiredSize)
+ }
+ }
+ }
+ }
+
+ private fun bindTo(query: SupportSQLiteProgram) {
+ for (index in 1 until bindingTypes.size) {
+ when (bindingTypes[index]) {
+ COLUMN_TYPE_LONG -> query.bindLong(index, longBindings[index])
+ COLUMN_TYPE_DOUBLE -> query.bindDouble(index, doubleBindings[index])
+ COLUMN_TYPE_STRING -> query.bindString(index, stringBindings[index]!!)
+ COLUMN_TYPE_BLOB -> query.bindBlob(index, blobBindings[index]!!)
+ COLUMN_TYPE_NULL -> query.bindNull(index)
+ }
+ }
+ }
+
+ companion object {
+ private const val COLUMN_TYPE_LONG = 1
+ private const val COLUMN_TYPE_DOUBLE = 2
+ private const val COLUMN_TYPE_STRING = 3
+ private const val COLUMN_TYPE_BLOB = 4
+ private const val COLUMN_TYPE_NULL = 5
}
}
}
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactory.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactory.android.kt
index ce1e650..7c8f88d 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactory.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/support/AutoClosingRoomOpenHelperFactory.android.kt
@@ -22,7 +22,6 @@
private val delegate: SupportSQLiteOpenHelper.Factory,
private val autoCloser: AutoCloser
) : SupportSQLiteOpenHelper.Factory {
- /** @return AutoClosingRoomOpenHelper instances. */
override fun create(
configuration: SupportSQLiteOpenHelper.Configuration
): AutoClosingRoomOpenHelper {
diff --git a/wear/compose/compose-foundation/api/current.txt b/wear/compose/compose-foundation/api/current.txt
index c75ad3e..ad4f841 100644
--- a/wear/compose/compose-foundation/api/current.txt
+++ b/wear/compose/compose-foundation/api/current.txt
@@ -301,6 +301,7 @@
public interface ScrollInfoProvider {
method public float getAnchorItemOffset();
+ method public float getLastItemOffset();
method public boolean isScrollAwayValid();
method public boolean isScrollInProgress();
method public boolean isScrollable();
@@ -308,12 +309,13 @@
property public abstract boolean isScrollAwayValid;
property public abstract boolean isScrollInProgress;
property public abstract boolean isScrollable;
+ property public abstract float lastItemOffset;
}
public final class ScrollInfoProviderKt {
- method public static androidx.wear.compose.foundation.ScrollInfoProvider toScrollAwayInfoProvider(androidx.compose.foundation.lazy.LazyListState);
- method public static androidx.wear.compose.foundation.ScrollInfoProvider toScrollAwayInfoProvider(androidx.compose.foundation.ScrollState);
- method public static androidx.wear.compose.foundation.ScrollInfoProvider toScrollAwayInfoProvider(androidx.wear.compose.foundation.lazy.ScalingLazyListState);
+ method public static androidx.wear.compose.foundation.ScrollInfoProvider ScrollInfoProvider(androidx.compose.foundation.lazy.LazyListState state);
+ method public static androidx.wear.compose.foundation.ScrollInfoProvider ScrollInfoProvider(androidx.compose.foundation.ScrollState state, optional float bottomButtonHeight);
+ method public static androidx.wear.compose.foundation.ScrollInfoProvider ScrollInfoProvider(androidx.wear.compose.foundation.lazy.ScalingLazyListState state);
}
public final class SwipeToDismissBoxDefaults {
diff --git a/wear/compose/compose-foundation/api/restricted_current.txt b/wear/compose/compose-foundation/api/restricted_current.txt
index c75ad3e..ad4f841 100644
--- a/wear/compose/compose-foundation/api/restricted_current.txt
+++ b/wear/compose/compose-foundation/api/restricted_current.txt
@@ -301,6 +301,7 @@
public interface ScrollInfoProvider {
method public float getAnchorItemOffset();
+ method public float getLastItemOffset();
method public boolean isScrollAwayValid();
method public boolean isScrollInProgress();
method public boolean isScrollable();
@@ -308,12 +309,13 @@
property public abstract boolean isScrollAwayValid;
property public abstract boolean isScrollInProgress;
property public abstract boolean isScrollable;
+ property public abstract float lastItemOffset;
}
public final class ScrollInfoProviderKt {
- method public static androidx.wear.compose.foundation.ScrollInfoProvider toScrollAwayInfoProvider(androidx.compose.foundation.lazy.LazyListState);
- method public static androidx.wear.compose.foundation.ScrollInfoProvider toScrollAwayInfoProvider(androidx.compose.foundation.ScrollState);
- method public static androidx.wear.compose.foundation.ScrollInfoProvider toScrollAwayInfoProvider(androidx.wear.compose.foundation.lazy.ScalingLazyListState);
+ method public static androidx.wear.compose.foundation.ScrollInfoProvider ScrollInfoProvider(androidx.compose.foundation.lazy.LazyListState state);
+ method public static androidx.wear.compose.foundation.ScrollInfoProvider ScrollInfoProvider(androidx.compose.foundation.ScrollState state, optional float bottomButtonHeight);
+ method public static androidx.wear.compose.foundation.ScrollInfoProvider ScrollInfoProvider(androidx.wear.compose.foundation.lazy.ScalingLazyListState state);
}
public final class SwipeToDismissBoxDefaults {
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/ScrollInfoProvider.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/ScrollInfoProvider.kt
index 6fb7c9c..6cfbb43 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/ScrollInfoProvider.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/ScrollInfoProvider.kt
@@ -19,10 +19,13 @@
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.ui.util.fastFirstOrNull
+import androidx.compose.ui.util.fastForEach
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType
+import androidx.wear.compose.foundation.lazy.ScalingLazyListItemInfo
import androidx.wear.compose.foundation.lazy.ScalingLazyListState
import androidx.wear.compose.foundation.lazy.startOffset
@@ -58,33 +61,45 @@
* returned offset is Float.NaN.
*/
val anchorItemOffset: Float
+
+ /**
+ * The amount of space between the last item (which may not be visible) and the bottom edge of
+ * the viewport. This is always greater or equal to 0, if there is no (or negative) room
+ * (including the case in which the last item is not on screen), 0 should be returned.
+ */
+ val lastItemOffset: Float
}
/**
- * Extension function for creating a [ScrollInfoProvider] from a [ScalingLazyListState], for use
- * with [ScalingLazyColumn] - used to create a ScrollAway modifier directly that can be applied to
- * an object that appears at the top of the screen, to scroll it away vertically when the
+ * Function for creating a [ScrollInfoProvider] from a [ScalingLazyListState], for use with
+ * [ScalingLazyColumn] - used to create a ScrollAway modifier directly that can be applied to an
+ * object that appears at the top of the screen, to scroll it away vertically when the
* [ScalingLazyColumn] is scrolled upwards.
+ *
+ * @param state
*/
-fun ScalingLazyListState.toScrollAwayInfoProvider(): ScrollInfoProvider =
- ScalingLazyListStateScrollInfoProvider(this)
+fun ScrollInfoProvider(state: ScalingLazyListState): ScrollInfoProvider =
+ ScalingLazyListStateScrollInfoProvider(state)
/**
- * Extension function for creating a [ScrollInfoProvider] from a [LazyListState], for use with
- * [LazyColumn] - used to create a ScrollAway modifier directly that can be applied to an object
- * that appears at the top of the screen, to scroll it away vertically when the [LazyColumn] is
- * scrolled upwards.
+ * Function for creating a [ScrollInfoProvider] from a [LazyListState], for use with [LazyColumn] -
+ * used to create a ScrollAway modifier directly that can be applied to an object that appears at
+ * the top of the screen, to scroll it away vertically when the [LazyColumn] is scrolled upwards.
*/
-fun LazyListState.toScrollAwayInfoProvider(): ScrollInfoProvider =
- LazyListStateScrollInfoProvider(this)
+fun ScrollInfoProvider(state: LazyListState): ScrollInfoProvider =
+ LazyListStateScrollInfoProvider(state)
/**
- * Extension function for creating a [ScrollInfoProvider] from a [ScrollState], for use with
- * [Column] - used to create a ScrollAway modifier directly that can be applied to an object that
- * appears at the top of the screen, to scroll it away vertically when the [Column] is scrolled
- * upwards.
+ * Function for creating a [ScrollInfoProvider] from a [ScrollState], for use with [Column] - used
+ * to create a ScrollAway modifier directly that can be applied to an object that appears at the top
+ * of the screen, to scroll it away vertically when the [Column] is scrolled upwards.
+ *
+ * @param state the [ScrollState] to use as the base for creating the [ScrollInfoProvider]
+ * @param bottomButtonHeight optional parameter to specify the size of a bottom button if one is
+ * provided.
*/
-fun ScrollState.toScrollAwayInfoProvider(): ScrollInfoProvider = ScrollStateScrollInfoProvider(this)
+fun ScrollInfoProvider(state: ScrollState, bottomButtonHeight: Float = 0f): ScrollInfoProvider =
+ ScrollStateScrollInfoProvider(state, bottomButtonHeight)
// Implementation of [ScrollAwayInfoProvider] for [ScalingLazyColumn].
// Being in Foundation, this implementation has access to the ScalingLazyListState
@@ -118,10 +133,25 @@
} ?: Float.NaN
}
+ override val lastItemOffset: Float
+ get() {
+ val screenHeightPx = state.config.value?.viewportHeightPx ?: 0
+ var lastItemInfo: ScalingLazyListItemInfo? = null
+ state.layoutInfo.visibleItemsInfo.fastForEach { ii ->
+ if (ii.index == state.layoutInfo.totalItemsCount - 1) lastItemInfo = ii
+ }
+ return lastItemInfo?.let {
+ val bottomEdge = it.offset + screenHeightPx / 2 + it.size / 2
+ (screenHeightPx - bottomEdge).toFloat().coerceAtLeast(0f)
+ } ?: 0f
+ }
+
override fun toString(): String {
- return "ScalingLazyColumnScrollAwayInfoProvider(isScrollAwayValid=$isScrollAwayValid, " +
+ return "ScalingLazyListStateScrollInfoProvider(isScrollAwayValid=$isScrollAwayValid, " +
+ "isScrollable=$isScrollable," +
"isScrollInProgress=$isScrollInProgress, " +
- "trackedItemOffset=$anchorItemOffset)"
+ "anchorItemOffset=$anchorItemOffset, " +
+ "lastItemOffset=$lastItemOffset)"
}
private var initialStartOffset: Float? = null
@@ -144,15 +174,33 @@
.fastFirstOrNull { it.index == 0 }
?.let { -it.offset.toFloat() } ?: Float.NaN
+ override val lastItemOffset: Float
+ get() {
+ val screenHeightPx = state.layoutInfo.viewportSize.height
+ var lastItemInfo: LazyListItemInfo? = null
+ state.layoutInfo.visibleItemsInfo.fastForEach { ii ->
+ if (ii.index == state.layoutInfo.totalItemsCount - 1) lastItemInfo = ii
+ }
+ return lastItemInfo?.let {
+ val bottomEdge = it.offset + it.size - state.layoutInfo.viewportStartOffset
+ (screenHeightPx - bottomEdge).toFloat().coerceAtLeast(0f)
+ } ?: 0f
+ }
+
override fun toString(): String {
- return "LazyColumnScrollAwayInfoProvider(isScrollAwayValid=$isScrollAwayValid, " +
+ return "LazyListStateScrollInfoProvider(isScrollAwayValid=$isScrollAwayValid, " +
+ "isScrollable=$isScrollable," +
"isScrollInProgress=$isScrollInProgress, " +
- "trackedItemOffset=$anchorItemOffset)"
+ "anchorItemOffset=$anchorItemOffset, " +
+ "lastItemOffset=$lastItemOffset)"
}
}
// Implementation of [ScrollAwayInfoProvider] for [Column]
-private class ScrollStateScrollInfoProvider(val state: ScrollState) : ScrollInfoProvider {
+private class ScrollStateScrollInfoProvider(
+ val state: ScrollState,
+ val bottomButtonHeight: Float = 0f
+) : ScrollInfoProvider {
override val isScrollAwayValid: Boolean
get() = true
@@ -170,8 +218,17 @@
override val anchorItemOffset: Float
get() = state.value.toFloat()
- override fun toString(): String =
- "DefaultScrollAwayInfoProvider(isScrollAwayValid=$isScrollAwayValid, " +
+ override val lastItemOffset: Float
+ get() {
+ return if (state.maxValue == Int.MAX_VALUE || bottomButtonHeight == 0f) 0f
+ else (state.value - state.maxValue + bottomButtonHeight).coerceAtLeast(0f)
+ }
+
+ override fun toString(): String {
+ return "ScrollStateScrollInfoProvider(isScrollAwayValid=$isScrollAwayValid, " +
+ "isScrollable=$isScrollable," +
"isScrollInProgress=$isScrollInProgress, " +
- "trackedItemOffset=$anchorItemOffset)"
+ "anchorItemOffset=$anchorItemOffset, " +
+ "lastItemOffset=$lastItemOffset)"
+ }
}
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index e847dac..2b55821 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -630,10 +630,12 @@
}
public final class ScreenScaffoldKt {
- method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.compose.foundation.lazy.LazyListState scrollState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.compose.foundation.lazy.LazyListState scrollState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> scrollIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? bottomButton, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.compose.foundation.ScrollState scrollState, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, float bottomButtonHeight, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void ScreenScaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional androidx.wear.compose.foundation.ScrollInfoProvider? scrollInfoProvider, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? bottomButton, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void ScreenScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, androidx.wear.compose.foundation.ScrollInfoProvider scrollInfoProvider, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
}
public enum ScreenStage {
@@ -658,6 +660,11 @@
method @androidx.compose.runtime.Composable public static void ScrollIndicator(androidx.wear.compose.foundation.lazy.ScalingLazyListState state, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> positionAnimationSpec);
}
+ public final class SegmentedCircularProgressIndicatorKt {
+ method @androidx.compose.runtime.Composable public static void SegmentedCircularProgressIndicator(@IntRange(from=1L) int segmentCount, kotlin.jvm.functions.Function0<java.lang.Float> progress, optional androidx.compose.ui.Modifier modifier, optional float startAngle, optional float endAngle, optional androidx.wear.compose.material3.ProgressIndicatorColors colors, optional float strokeWidth, optional float gapSize);
+ method @androidx.compose.runtime.Composable public static void SegmentedCircularProgressIndicator(@IntRange(from=1L) int segmentCount, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> completed, optional androidx.compose.ui.Modifier modifier, optional float startAngle, optional float endAngle, optional androidx.wear.compose.material3.ProgressIndicatorColors colors, optional float strokeWidth, optional float gapSize);
+ }
+
public final class ShapeDefaults {
method public androidx.compose.foundation.shape.RoundedCornerShape getExtraLarge();
method public androidx.compose.foundation.shape.RoundedCornerShape getExtraSmall();
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index e847dac..2b55821 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -630,10 +630,12 @@
}
public final class ScreenScaffoldKt {
- method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.compose.foundation.lazy.LazyListState scrollState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.compose.foundation.lazy.LazyListState scrollState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> scrollIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? bottomButton, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.compose.foundation.ScrollState scrollState, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, float bottomButtonHeight, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void ScreenScaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional androidx.wear.compose.foundation.ScrollInfoProvider? scrollInfoProvider, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
- method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void ScreenScaffold(androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? bottomButton, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void ScreenScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, androidx.wear.compose.foundation.ScrollInfoProvider scrollInfoProvider, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? scrollIndicator, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
}
public enum ScreenStage {
@@ -658,6 +660,11 @@
method @androidx.compose.runtime.Composable public static void ScrollIndicator(androidx.wear.compose.foundation.lazy.ScalingLazyListState state, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> positionAnimationSpec);
}
+ public final class SegmentedCircularProgressIndicatorKt {
+ method @androidx.compose.runtime.Composable public static void SegmentedCircularProgressIndicator(@IntRange(from=1L) int segmentCount, kotlin.jvm.functions.Function0<java.lang.Float> progress, optional androidx.compose.ui.Modifier modifier, optional float startAngle, optional float endAngle, optional androidx.wear.compose.material3.ProgressIndicatorColors colors, optional float strokeWidth, optional float gapSize);
+ method @androidx.compose.runtime.Composable public static void SegmentedCircularProgressIndicator(@IntRange(from=1L) int segmentCount, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> completed, optional androidx.compose.ui.Modifier modifier, optional float startAngle, optional float endAngle, optional androidx.wear.compose.material3.ProgressIndicatorColors colors, optional float strokeWidth, optional float gapSize);
+ }
+
public final class ShapeDefaults {
method public androidx.compose.foundation.shape.RoundedCornerShape getExtraLarge();
method public androidx.compose.foundation.shape.RoundedCornerShape getExtraSmall();
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
index 6c02d1e..9960e0a 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
@@ -16,79 +16,76 @@
package androidx.wear.compose.material3.demos
+import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
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.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastForEach
+import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
-import androidx.wear.compose.foundation.lazy.ScalingLazyListItemInfo
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import androidx.wear.compose.foundation.rememberActiveFocusRequester
+import androidx.wear.compose.foundation.rotary.RotaryScrollableDefaults
+import androidx.wear.compose.foundation.rotary.rotaryScrollable
import androidx.wear.compose.integration.demos.common.AdaptiveScreen
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.ButtonDefaults.buttonColors
import androidx.wear.compose.material3.Card
import androidx.wear.compose.material3.CompactButton
import androidx.wear.compose.material3.EdgeButton
+import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.Text
@Composable
-fun EdgeButtonBelowListDemo() {
- // NOTE: This demo recomposes the Edge Button when it's appearing / disappearing.
+fun EdgeButtonBelowLazyColumnDemo() {
+ val labels =
+ listOf(
+ "Hi",
+ "Hello World",
+ "Hello world again?",
+ "More content as we add stuff",
+ "I don't know if this will fit now, testing",
+ "Really long text that it's going to take multiple lines",
+ "And now we are really pushing it because the screen is really small",
+ )
+ val selectedLabel = remember { mutableIntStateOf(0) }
AdaptiveScreen {
- val state = rememberScalingLazyListState()
- val screenHeightPx =
- with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
- val heightPx = remember {
- derivedStateOf {
- val marginPx = 5f // !?
- var lastItemInfo: ScalingLazyListItemInfo? = null
- state.layoutInfo.visibleItemsInfo.fastForEach { ii ->
- if (ii.index == state.layoutInfo.totalItemsCount - 1) lastItemInfo = ii
+ val state = rememberLazyListState()
+ ScreenScaffold(
+ scrollState = state,
+ bottomButton = {
+ EdgeButton(
+ onClick = {},
+ buttonHeight = ButtonDefaults.EdgeButtonHeightLarge,
+ colors = buttonColors(containerColor = Color.DarkGray)
+ ) {
+ Text(labels[selectedLabel.intValue], color = Color.White)
}
- lastItemInfo?.let {
- val bottomEdge = it.offset + screenHeightPx / 2 + it.size / 2
- (screenHeightPx - bottomEdge - marginPx).coerceAtLeast(0f)
- } ?: 0f
}
- }
-
- val labels =
- listOf(
- "Hi",
- "Hello World",
- "Hello world again?",
- "More content as we add stuff",
- "I don't know if this will fit now, testing",
- "Really long text that it's going to take multiple lines",
- "And now we are really pushing it because the screen is really small",
- )
- val selectedLabel = remember { mutableIntStateOf(0) }
-
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
- ScalingLazyColumn(
+ ) {
+ LazyColumn(
state = state,
modifier = Modifier.fillMaxSize(),
- autoCentering = null,
- contentPadding = PaddingValues(10.dp, 20.dp, 10.dp, 100.dp)
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ contentPadding = PaddingValues(10.dp, 20.dp, 10.dp, 80.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
) {
items(labels.size) {
Card(
@@ -99,30 +96,116 @@
}
}
}
- // We isolate the call to EdgeButton to a function so only that is recomposed when the
- // height changes.
- EdgeButtonCall(heightPx) {
- Text(
- labels[selectedLabel.intValue],
- color = Color.White,
- textAlign = TextAlign.Center,
- maxLines = 3,
- )
- }
}
}
}
@Composable
-private fun EdgeButtonCall(heightPx: State<Float>, content: @Composable RowScope.() -> Unit) {
- val heightDp = with(LocalDensity.current) { heightPx.value.toDp() }
- EdgeButton(
- onClick = {},
- buttonHeight = ButtonDefaults.EdgeButtonHeightLarge,
- colors = buttonColors(containerColor = Color.DarkGray),
- modifier = Modifier.height(heightDp),
- content = content
- )
+fun EdgeButtonBelowScalingLazyColumnDemo() {
+ val labels =
+ listOf(
+ "Hi",
+ "Hello World",
+ "Hello world again?",
+ "More content as we add stuff",
+ "I don't know if this will fit now, testing",
+ "Really long text that it's going to take multiple lines",
+ "And now we are really pushing it because the screen is really small",
+ )
+ val selectedLabel = remember { mutableIntStateOf(0) }
+
+ AdaptiveScreen {
+ val state = rememberScalingLazyListState()
+ ScreenScaffold(
+ scrollState = state,
+ bottomButton = {
+ EdgeButton(
+ onClick = {},
+ buttonHeight = ButtonDefaults.EdgeButtonHeightLarge,
+ colors = buttonColors(containerColor = Color.DarkGray)
+ ) {
+ Text(labels[selectedLabel.intValue], color = Color.White)
+ }
+ }
+ ) {
+ ScalingLazyColumn(
+ state = state,
+ modifier = Modifier.fillMaxSize(),
+ autoCentering = null,
+ contentPadding = PaddingValues(10.dp, 20.dp, 10.dp, 100.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ items(labels.size) {
+ Card(
+ onClick = { selectedLabel.intValue = it },
+ modifier = Modifier.fillMaxWidth(0.9f)
+ ) {
+ Text(labels[it])
+ }
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalWearFoundationApi::class)
+@Composable
+fun EdgeButtonBelowColumnDemo() {
+ val labels =
+ listOf(
+ "Hi",
+ "Hello World",
+ "Hello world again?",
+ "More content as we add stuff",
+ "I don't know if this will fit now, testing",
+ "Really long text that it's going to take multiple lines",
+ "And now we are really pushing it because the screen is really small",
+ )
+ val selectedLabel = remember { mutableIntStateOf(0) }
+ val bottomButtonHeight = ButtonDefaults.EdgeButtonHeightLarge
+
+ AdaptiveScreen {
+ val scrollState = rememberScrollState()
+ val focusRequester = rememberActiveFocusRequester()
+
+ ScreenScaffold(
+ scrollState = scrollState,
+ bottomButton = {
+ EdgeButton(
+ onClick = {},
+ buttonHeight = bottomButtonHeight,
+ colors = buttonColors(containerColor = Color.DarkGray)
+ ) {
+ Text(labels[selectedLabel.intValue], color = Color.White)
+ }
+ },
+ bottomButtonHeight = bottomButtonHeight
+ ) {
+ Column(
+ modifier =
+ Modifier.verticalScroll(scrollState)
+ .rotaryScrollable(
+ RotaryScrollableDefaults.behavior(
+ scrollableState = scrollState,
+ flingBehavior = ScrollableDefaults.flingBehavior()
+ ),
+ focusRequester = focusRequester
+ ),
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ repeat(labels.size) {
+ Card(
+ onClick = { selectedLabel.intValue = it },
+ modifier = Modifier.fillMaxWidth(0.9f)
+ ) {
+ Text(labels[it])
+ }
+ }
+ Spacer(Modifier.height(bottomButtonHeight))
+ }
+ }
+ }
}
@Suppress("PrimitiveInCollection")
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ProgressIndicatorDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ProgressIndicatorDemo.kt
index c1c15b3..0179e50 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ProgressIndicatorDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ProgressIndicatorDemo.kt
@@ -37,6 +37,8 @@
import androidx.wear.compose.material3.samples.FullScreenProgressIndicatorSample
import androidx.wear.compose.material3.samples.MediaButtonProgressIndicatorSample
import androidx.wear.compose.material3.samples.OverflowProgressIndicatorSample
+import androidx.wear.compose.material3.samples.SegmentedProgressIndicatorOnOffSample
+import androidx.wear.compose.material3.samples.SegmentedProgressIndicatorSample
import androidx.wear.compose.material3.samples.SmallValuesProgressIndicatorSample
val ProgressIndicatorDemos =
@@ -69,4 +71,8 @@
ComposableDemo("Small progress values") {
Centralize { SmallValuesProgressIndicatorSample() }
},
+ ComposableDemo("Segmented progress") { Centralize { SegmentedProgressIndicatorSample() } },
+ ComposableDemo("Progress segments on/off") {
+ Centralize { SegmentedProgressIndicatorOnOffSample() }
+ },
)
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollAwayDemos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollAwayDemos.kt
index f33edc4..ce9aa66 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollAwayDemos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollAwayDemos.kt
@@ -30,10 +30,10 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.ScrollInfoProvider
import androidx.wear.compose.foundation.rememberActiveFocusRequester
import androidx.wear.compose.foundation.rotary.RotaryScrollableDefaults
import androidx.wear.compose.foundation.rotary.rotaryScrollable
-import androidx.wear.compose.foundation.toScrollAwayInfoProvider
import androidx.wear.compose.integration.demos.common.Centralize
import androidx.wear.compose.integration.demos.common.ComposableDemo
import androidx.wear.compose.material3.FilledTonalButton
@@ -94,7 +94,7 @@
// default handling is unsuitable.
modifier =
Modifier.scrollAway(
- scrollInfoProvider = scrollState.toScrollAwayInfoProvider(),
+ scrollInfoProvider = ScrollInfoProvider(scrollState),
screenStage = {
if (scrollState.isScrollInProgress) ScreenStage.Scrolling
else ScreenStage.Idle
@@ -145,7 +145,7 @@
// default handling is unsuitable.
modifier =
Modifier.scrollAway(
- scrollInfoProvider = scrollState.toScrollAwayInfoProvider(),
+ scrollInfoProvider = ScrollInfoProvider(scrollState),
screenStage = {
if (scrollState.isScrollInProgress) ScreenStage.Scrolling
else ScreenStage.Idle
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index 7c13585..3e8e13e 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -25,6 +25,7 @@
import androidx.wear.compose.material3.samples.AnimatedTextSample
import androidx.wear.compose.material3.samples.AnimatedTextSampleButtonResponse
import androidx.wear.compose.material3.samples.AnimatedTextSampleSharedFontRegistry
+import androidx.wear.compose.material3.samples.EdgeButtonListSample
import androidx.wear.compose.material3.samples.EdgeButtonSample
import androidx.wear.compose.material3.samples.EdgeSwipeForSwipeToDismiss
import androidx.wear.compose.material3.samples.FixedFontSize
@@ -51,8 +52,17 @@
"Edge Button",
listOf(
ComposableDemo("Simple Edge Button") { EdgeButtonSample() },
+ ComposableDemo("Simple Edge Button below SLC") {
+ EdgeButtonListSample()
+ },
ComposableDemo("Edge Button Sizes") { EdgeButtonSizeDemo() },
- ComposableDemo("Edge Button Below List") { EdgeButtonBelowListDemo() },
+ ComposableDemo("Edge Button Below C") { EdgeButtonBelowColumnDemo() },
+ ComposableDemo("Edge Button Below LC") {
+ EdgeButtonBelowLazyColumnDemo()
+ },
+ ComposableDemo("Edge Button Below SLC") {
+ EdgeButtonBelowScalingLazyColumnDemo()
+ },
)
),
ComposableDemo("Button") { ButtonDemo() },
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt
index 0d49823..122d612 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt
@@ -18,16 +18,26 @@
import androidx.annotation.Sampled
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
import androidx.wear.compose.material3.ButtonDefaults
+import androidx.wear.compose.material3.ButtonDefaults.buttonColors
+import androidx.wear.compose.material3.Card
import androidx.wear.compose.material3.EdgeButton
import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.Text
@Sampled
@@ -48,3 +58,33 @@
}
}
}
+
+@Sampled
+@Composable
+fun EdgeButtonListSample() {
+ val state = rememberScalingLazyListState()
+ ScreenScaffold(
+ scrollState = state,
+ bottomButton = {
+ EdgeButton(
+ onClick = {},
+ buttonHeight = ButtonDefaults.EdgeButtonHeightLarge,
+ colors = buttonColors(containerColor = Color.DarkGray)
+ ) {
+ Text("Ok", textAlign = TextAlign.Center)
+ }
+ }
+ ) {
+ ScalingLazyColumn(
+ state = state,
+ modifier = Modifier.fillMaxSize(),
+ autoCentering = null,
+ contentPadding = PaddingValues(10.dp, 20.dp, 10.dp, 100.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ items(10) {
+ Card(onClick = {}, modifier = Modifier.fillMaxWidth(0.9f)) { Text("Item #$it") }
+ }
+ }
+ }
+}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ProgressIndicatorSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ProgressIndicatorSample.kt
index 7557fc9..9c732d4 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ProgressIndicatorSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ProgressIndicatorSample.kt
@@ -45,7 +45,7 @@
import androidx.wear.compose.material3.IconButtonDefaults
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.ProgressIndicatorDefaults
-import androidx.wear.compose.material3.ProgressIndicatorDefaults.FullScreenPadding
+import androidx.wear.compose.material3.SegmentedCircularProgressIndicator
@Sampled
@Composable
@@ -53,7 +53,7 @@
Box(
modifier =
Modifier.background(MaterialTheme.colorScheme.background)
- .padding(FullScreenPadding)
+ .padding(ProgressIndicatorDefaults.FullScreenPadding)
.fillMaxSize()
) {
CircularProgressIndicator(
@@ -114,7 +114,7 @@
Box(
modifier =
Modifier.background(MaterialTheme.colorScheme.background)
- .padding(FullScreenPadding)
+ .padding(ProgressIndicatorDefaults.FullScreenPadding)
.fillMaxSize()
) {
CircularProgressIndicator(
@@ -144,7 +144,7 @@
CircularProgressIndicator(
// Small progress values like 2% will be rounded up to at least the stroke width.
progress = { 0.02f },
- modifier = Modifier.fillMaxSize().padding(FullScreenPadding),
+ modifier = Modifier.fillMaxSize().padding(ProgressIndicatorDefaults.FullScreenPadding),
startAngle = 120f,
endAngle = 60f,
strokeWidth = 10.dp,
@@ -156,3 +156,45 @@
)
}
}
+
+@Sampled
+@Composable
+fun SegmentedProgressIndicatorSample() {
+ Box(
+ modifier =
+ Modifier.background(MaterialTheme.colorScheme.background)
+ .padding(ProgressIndicatorDefaults.FullScreenPadding)
+ .fillMaxSize()
+ ) {
+ SegmentedCircularProgressIndicator(
+ segmentCount = 5,
+ progress = { 0.5f },
+ colors =
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Green,
+ trackColor = Color.Green.copy(alpha = 0.5f)
+ )
+ )
+ }
+}
+
+@Sampled
+@Composable
+fun SegmentedProgressIndicatorOnOffSample() {
+ Box(
+ modifier =
+ Modifier.background(MaterialTheme.colorScheme.background)
+ .padding(ProgressIndicatorDefaults.FullScreenPadding)
+ .fillMaxSize()
+ ) {
+ SegmentedCircularProgressIndicator(
+ segmentCount = 5,
+ completed = { it % 2 != 0 },
+ colors =
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Green,
+ trackColor = Color.Green.copy(alpha = 0.5f)
+ )
+ )
+ }
+}
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollAwaySample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollAwaySample.kt
index 0d8de7d..08d9fc6 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollAwaySample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollAwaySample.kt
@@ -25,10 +25,10 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.ScrollInfoProvider
import androidx.wear.compose.foundation.lazy.AutoCenteringParams
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
-import androidx.wear.compose.foundation.toScrollAwayInfoProvider
import androidx.wear.compose.material3.FilledTonalButton
import androidx.wear.compose.material3.ListHeader
import androidx.wear.compose.material3.ScreenStage
@@ -70,7 +70,7 @@
// [Modifier.scrollAway] directly.
modifier =
Modifier.scrollAway(
- scrollInfoProvider = state.toScrollAwayInfoProvider(),
+ scrollInfoProvider = ScrollInfoProvider(state),
screenStage = {
if (state.isScrollInProgress) ScreenStage.Scrolling else ScreenStage.Idle
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt
index eee671e..d37e1e2 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -33,6 +34,7 @@
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
@@ -67,24 +69,53 @@
@Test
fun edge_button_xsmall() =
- verifyScreenshot() { BasicEdgeButton(height = ButtonDefaults.EdgeButtonHeightExtraSmall) }
+ verifyScreenshot() {
+ BasicEdgeButton(buttonHeight = ButtonDefaults.EdgeButtonHeightExtraSmall)
+ }
@Test
fun edge_button_small() =
- verifyScreenshot() { BasicEdgeButton(height = ButtonDefaults.EdgeButtonHeightSmall) }
+ verifyScreenshot() { BasicEdgeButton(buttonHeight = ButtonDefaults.EdgeButtonHeightSmall) }
@Test
fun edge_button_medium() =
- verifyScreenshot() { BasicEdgeButton(height = ButtonDefaults.EdgeButtonHeightMedium) }
+ verifyScreenshot() { BasicEdgeButton(buttonHeight = ButtonDefaults.EdgeButtonHeightMedium) }
@Test
fun edge_button_large() =
- verifyScreenshot() { BasicEdgeButton(height = ButtonDefaults.EdgeButtonHeightLarge) }
+ verifyScreenshot() { BasicEdgeButton(buttonHeight = ButtonDefaults.EdgeButtonHeightLarge) }
@Test
fun edge_button_disabled() =
verifyScreenshot() {
- BasicEdgeButton(height = ButtonDefaults.EdgeButtonHeightMedium, enabled = false)
+ BasicEdgeButton(buttonHeight = ButtonDefaults.EdgeButtonHeightMedium, enabled = false)
+ }
+
+ @Test
+ fun edge_button_small_space_very_limited() =
+ verifyScreenshot() {
+ BasicEdgeButton(
+ buttonHeight = ButtonDefaults.EdgeButtonHeightSmall,
+ constrainedHeight = 10.dp
+ )
+ }
+
+ @Test
+ fun edge_button_small_space_limited() =
+ verifyScreenshot() {
+ BasicEdgeButton(
+ buttonHeight = ButtonDefaults.EdgeButtonHeightSmall,
+ constrainedHeight = 30.dp
+ )
+ }
+
+ @Test
+ fun edge_button_small_slightly_limited() =
+ verifyScreenshot() {
+ BasicEdgeButton(
+ buttonHeight = ButtonDefaults.EdgeButtonHeightSmall,
+ constrainedHeight = 40.dp
+ )
}
private val LONG_TEXT =
@@ -94,23 +125,34 @@
@Test
fun edge_button_xsmall_long_text() =
verifyScreenshot() {
- BasicEdgeButton(height = ButtonDefaults.EdgeButtonHeightExtraSmall, text = LONG_TEXT)
+ BasicEdgeButton(
+ buttonHeight = ButtonDefaults.EdgeButtonHeightExtraSmall,
+ text = LONG_TEXT
+ )
}
@Test
fun edge_button_large_long_text() =
verifyScreenshot() {
- BasicEdgeButton(height = ButtonDefaults.EdgeButtonHeightLarge, text = LONG_TEXT)
+ BasicEdgeButton(buttonHeight = ButtonDefaults.EdgeButtonHeightLarge, text = LONG_TEXT)
}
@Composable
- private fun BasicEdgeButton(height: Dp, enabled: Boolean = true, text: String = "Text") {
+ private fun BasicEdgeButton(
+ buttonHeight: Dp,
+ constrainedHeight: Dp? = null,
+ enabled: Boolean = true,
+ text: String = "Text"
+ ) {
Box(Modifier.fillMaxSize()) {
EdgeButton(
onClick = { /* Do something */ },
enabled = enabled,
- buttonHeight = height,
- modifier = Modifier.align(Alignment.BottomEnd).testTag(TEST_TAG)
+ buttonHeight = buttonHeight,
+ modifier =
+ Modifier.align(Alignment.BottomEnd)
+ .testTag(TEST_TAG)
+ .then(constrainedHeight?.let { Modifier.height(it) } ?: Modifier)
) {
BasicText(text)
}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorScreenshotTest.kt
index e6ad406..2cc06b3 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorScreenshotTest.kt
@@ -129,6 +129,28 @@
)
}
+ @Test
+ fun segmented_progress_indicator_with_progress() = verifyScreenshot {
+ SegmentedCircularProgressIndicator(
+ progress = { 0.5f },
+ segmentCount = 5,
+ modifier = Modifier.aspectRatio(1f).testTag(TEST_TAG),
+ startAngle = 120f,
+ endAngle = 60f,
+ )
+ }
+
+ @Test
+ fun segmented_progress_indicator_on_off() = verifyScreenshot {
+ SegmentedCircularProgressIndicator(
+ segmentCount = 6,
+ completed = { it % 2 == 0 },
+ modifier = Modifier.aspectRatio(1f).testTag(TEST_TAG),
+ startAngle = 120f,
+ endAngle = 60f,
+ )
+ }
+
private fun verifyScreenshot(content: @Composable () -> Unit) {
rule.setContentWithTheme {
CompositionLocalProvider(
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt
index b1b05a8..a78804c 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt
@@ -134,8 +134,8 @@
)
}
rule.waitForIdle()
- // Color should take approximately a quarter of what it normally takes
- // (a little bit less), eg 25% / 4 ≈ 6%.
+ // Color should take approximately a quarter of the full screen color percentages,
+ // eg 25% / 4 ≈ 6%.
rule
.onNodeWithTag(TEST_TAG)
.captureToImage()
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScaffoldTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScaffoldTest.kt
index a5f4bc9..ab8ec90 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScaffoldTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScaffoldTest.kt
@@ -19,14 +19,20 @@
import android.os.Build
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.testutils.assertContainsColor
import androidx.compose.testutils.assertDoesNotContainColor
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.captureToImage
@@ -35,10 +41,13 @@
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.filters.SdkSuppress
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import com.google.common.truth.Truth.assertThat
+import junit.framework.TestCase.assertEquals
import org.junit.Rule
import org.junit.Test
@@ -154,8 +163,104 @@
rule.onNodeWithTag(TEST_TAG).captureToImage().assertContainsColor(timeTextColor)
}
+ @Test
+ fun no_initial_room_for_bottom_button() {
+ var spaceAvailable: Int = Int.MAX_VALUE
+
+ rule.setContentWithTheme {
+ // Ensure we use the same size no mater where this is run.
+ Box(Modifier.size(300.dp)) {
+ TestScreenScaffold(scrollIndicatorColor = Color.Blue, timeTextColor = Color.Red) {
+ BoxWithConstraints {
+ // Check how much space we have for the bottom button
+ spaceAvailable = constraints.maxHeight
+ }
+ }
+ }
+ }
+
+ assertEquals(0, spaceAvailable)
+ }
+
+ @Test
+ fun plenty_of_room_for_bottom_button_after_scroll() {
+ var spaceAvailable: Int = Int.MAX_VALUE
+ var expectedSpace = 0f
+
+ val screenSize = 300.dp
+ rule.setContentWithTheme {
+ // The available space is half the screen size minus half a Button height (converting
+ // dps to pixels).
+ expectedSpace =
+ with(LocalDensity.current) { ((screenSize - ButtonDefaults.Height) / 2).toPx() }
+
+ Box(Modifier.size(screenSize)) {
+ TestScreenScaffold(scrollIndicatorColor = Color.Blue, timeTextColor = Color.Red) {
+ // Check how much space we have for the bottom button
+ BoxWithConstraints { spaceAvailable = constraints.maxHeight }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(SCROLL_TAG).performTouchInput { repeat(5) { swipeUp() } }
+ rule.waitForIdle()
+
+ // Use floats so we can specify a pixel of tolerance.
+ assertThat(spaceAvailable.toFloat()).isWithin(1f).of(expectedSpace)
+ }
+
+ @Test
+ fun no_initial_room_for_bottom_button_lc() {
+ var spaceAvailable: Int = Int.MAX_VALUE
+
+ rule.setContentWithTheme {
+ // Ensure we use the same size no mater where this is run.
+ Box(Modifier.size(300.dp)) {
+ TestBottomButtonLC() {
+ BoxWithConstraints {
+ // Check how much space we have for the bottom button
+ spaceAvailable = constraints.maxHeight
+ }
+ }
+ }
+ }
+
+ assertEquals(0, spaceAvailable)
+ }
+
+ @Test fun no_room_for_bottom_button_after_scroll_lc() = check_bottom_button_lc(0.dp)
+
+ @Test fun some_room_for_bottom_button_after_scroll_lc() = check_bottom_button_lc(50.dp)
+
+ private fun check_bottom_button_lc(verticalPadding: Dp = 0.dp) {
+ var spaceAvailable: Int = Int.MAX_VALUE
+ var expectedSpace: Float = Float.MAX_VALUE
+
+ val screenSize = 300.dp
+ rule.setContentWithTheme {
+ expectedSpace = with(LocalDensity.current) { verticalPadding.toPx() }
+
+ Box(Modifier.size(screenSize)) {
+ TestBottomButtonLC(verticalPadding) {
+ // Check how much space we have for the bottom button
+ BoxWithConstraints { spaceAvailable = constraints.maxHeight }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(SCROLL_TAG).performTouchInput { repeat(5) { swipeUp() } }
+ rule.waitForIdle()
+
+ // Use floats so we can specify a pixel of tolerance.
+ assertThat(spaceAvailable.toFloat()).isWithin(1f).of(expectedSpace)
+ }
+
@Composable
- private fun TestScreenScaffold(scrollIndicatorColor: Color, timeTextColor: Color) {
+ private fun TestScreenScaffold(
+ scrollIndicatorColor: Color,
+ timeTextColor: Color,
+ bottomButton: @Composable BoxScope.() -> Unit = {}
+ ) {
AppScaffold {
val scrollState = rememberScalingLazyListState()
ScreenScaffold(
@@ -169,7 +274,8 @@
.background(scrollIndicatorColor)
)
},
- timeText = { Box(Modifier.size(20.dp).background(timeTextColor)) }
+ timeText = { Box(Modifier.size(20.dp).background(timeTextColor)) },
+ bottomButton = bottomButton
) {
ScalingLazyColumn(
state = scrollState,
@@ -185,6 +291,34 @@
}
}
}
+
+ @Composable
+ private fun TestBottomButtonLC(
+ verticalPadding: Dp = 0.dp,
+ bottomButton: @Composable BoxScope.() -> Unit = {}
+ ) {
+ AppScaffold {
+ val scrollState = rememberLazyListState()
+ ScreenScaffold(
+ modifier = Modifier.testTag(TEST_TAG),
+ scrollState = scrollState,
+ bottomButton = bottomButton
+ ) {
+ LazyColumn(
+ state = scrollState,
+ modifier = Modifier.fillMaxSize().background(Color.Black).testTag(SCROLL_TAG),
+ contentPadding = PaddingValues(horizontal = 10.dp, vertical = verticalPadding)
+ ) {
+ items(10) {
+ Button(
+ onClick = {},
+ label = { Text("Item ${it + 1}") },
+ )
+ }
+ }
+ }
+ }
+ }
}
private const val CONTENT_MESSAGE = "The Content"
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt
index 5dfc424..90674cb 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt
@@ -47,11 +47,11 @@
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
+import androidx.wear.compose.foundation.ScrollInfoProvider
import androidx.wear.compose.foundation.lazy.AutoCenteringParams
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.ScalingLazyListState
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
-import androidx.wear.compose.foundation.toScrollAwayInfoProvider
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -199,7 +199,7 @@
TimeText(
modifier =
Modifier.scrollAway(
- scrollInfoProvider = scrollState.toScrollAwayInfoProvider(),
+ scrollInfoProvider = ScrollInfoProvider(scrollState),
screenStage = {
if (scrollState.isScrollInProgress) ScreenStage.Scrolling
else ScreenStage.Idle
@@ -225,7 +225,7 @@
TimeText(
modifier =
Modifier.scrollAway(
- scrollInfoProvider = scrollState.toScrollAwayInfoProvider(),
+ scrollInfoProvider = ScrollInfoProvider(scrollState),
screenStage = {
if (scrollState.isScrollInProgress) ScreenStage.Scrolling
else ScreenStage.Idle
@@ -256,7 +256,7 @@
contentColor = timeTextColor,
modifier =
Modifier.scrollAway(
- scrollInfoProvider = scrollState.toScrollAwayInfoProvider(),
+ scrollInfoProvider = ScrollInfoProvider(scrollState),
screenStage = {
if (scrollState.isScrollInProgress) ScreenStage.Scrolling
else ScreenStage.Idle
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SegmentedCircularProgressIndicatorTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SegmentedCircularProgressIndicatorTest.kt
new file mode 100644
index 0000000..58e2c21
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SegmentedCircularProgressIndicatorTest.kt
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.testutils.assertDoesNotContainColor
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.ProgressBarRangeInfo
+import androidx.compose.ui.semantics.progressBarRangeInfo
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.assertRangeInfoEquals
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+
+class SegmentedCircularProgressIndicatorTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun supports_testtag() {
+ setContentWithTheme {
+ SegmentedCircularProgressIndicator(
+ segmentCount = 5,
+ progress = { 0.5f },
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertExists()
+ }
+
+ @Test
+ fun allows_semantics_to_be_added_correctly() {
+ val progress = mutableStateOf(0f)
+
+ setContentWithTheme {
+ SegmentedCircularProgressIndicator(
+ progress = { progress.value },
+ segmentCount = 5,
+ modifier =
+ Modifier.testTag(TEST_TAG).semantics {
+ progressBarRangeInfo = ProgressBarRangeInfo(progress.value, 0f..1f)
+ },
+ )
+ }
+
+ rule.onNodeWithTag(TEST_TAG).assertRangeInfoEquals(ProgressBarRangeInfo(0f, 0f..1f))
+
+ rule.runOnIdle { progress.value = 0.5f }
+
+ rule.onNodeWithTag(TEST_TAG).assertRangeInfoEquals(ProgressBarRangeInfo(0.5f, 0f..1f))
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun progress_full_contains_progress_color() {
+ setContentWithTheme {
+ SegmentedCircularProgressIndicator(
+ progress = { 1f },
+ segmentCount = 5,
+ modifier = Modifier.testTag(TEST_TAG),
+ colors =
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Yellow,
+ trackColor = Color.Red
+ ),
+ )
+ }
+ rule.waitForIdle()
+ // by default fully filled progress approximately takes 25% of the control.
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Yellow, 20f..25f)
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertDoesNotContainColor(Color.Red)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun progress_zero_contains_track_color() {
+ setContentWithTheme {
+ SegmentedCircularProgressIndicator(
+ progress = { 0f },
+ segmentCount = 5,
+ modifier = Modifier.testTag(TEST_TAG),
+ colors =
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Yellow,
+ trackColor = Color.Red
+ ),
+ )
+ }
+ rule.waitForIdle()
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertDoesNotContainColor(Color.Yellow)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Red, 20f..25f)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun change_start_end_angle() {
+ setContentWithTheme {
+ SegmentedCircularProgressIndicator(
+ progress = { 0.5f },
+ segmentCount = 5,
+ modifier = Modifier.testTag(TEST_TAG),
+ startAngle = 0f,
+ endAngle = 180f,
+ colors =
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Yellow,
+ trackColor = Color.Red
+ ),
+ )
+ }
+ rule.waitForIdle()
+ // Color should take approximately a quarter of the full screen color percentages,
+ // eg 25% / 4 ≈ 6%.
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Yellow, 4f..8f)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Red, 4f..8f)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun set_small_stroke_width() {
+ setContentWithTheme {
+ SegmentedCircularProgressIndicator(
+ progress = { 0.5f },
+ segmentCount = 5,
+ modifier = Modifier.testTag(TEST_TAG),
+ strokeWidth = 4.dp,
+ colors =
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Yellow,
+ trackColor = Color.Red
+ ),
+ )
+ }
+ rule.waitForIdle()
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Yellow, 2f..6f)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Red, 2f..6f)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun set_large_stroke_width() {
+ setContentWithTheme {
+ SegmentedCircularProgressIndicator(
+ progress = { 0.5f },
+ segmentCount = 5,
+ modifier = Modifier.testTag(TEST_TAG),
+ strokeWidth = 36.dp,
+ colors =
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Yellow,
+ trackColor = Color.Red
+ ),
+ )
+ }
+ rule.waitForIdle()
+ // Because of the stroke cap, progress color takes same amount as track color.
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Yellow, 15f..20f)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Red, 15f..20f)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun set_segments_on_off() {
+ setContentWithTheme {
+ SegmentedCircularProgressIndicator(
+ segmentCount = 6,
+ completed = { it % 2 != 0 },
+ modifier = Modifier.testTag(TEST_TAG),
+ colors =
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Yellow,
+ trackColor = Color.Red
+ ),
+ strokeWidth = 36.dp,
+ )
+ }
+
+ rule.waitForIdle()
+ // Because of the stroke cap, progress color takes same amount as track color.
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Yellow, 15f..20f)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Red, 15f..20f)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun set_segments_all_on() {
+ setContentWithTheme {
+ SegmentedCircularProgressIndicator(
+ segmentCount = 6,
+ completed = { true },
+ modifier = Modifier.testTag(TEST_TAG),
+ colors =
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Yellow,
+ trackColor = Color.Red
+ ),
+ )
+ }
+
+ rule.waitForIdle()
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertDoesNotContainColor(Color.Red)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Yellow, 20f..25f)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun set_segments_all_off() {
+ setContentWithTheme {
+ SegmentedCircularProgressIndicator(
+ segmentCount = 6,
+ completed = { false },
+ modifier = Modifier.testTag(TEST_TAG),
+ colors =
+ ProgressIndicatorDefaults.colors(
+ indicatorColor = Color.Yellow,
+ trackColor = Color.Red
+ ),
+ )
+ }
+
+ rule.waitForIdle()
+ rule.onNodeWithTag(TEST_TAG).captureToImage().assertDoesNotContainColor(Color.Yellow)
+ rule
+ .onNodeWithTag(TEST_TAG)
+ .captureToImage()
+ .assertColorInPercentageRange(Color.Red, 20f..25f)
+ }
+
+ private fun setContentWithTheme(composable: @Composable BoxScope.() -> Unit) {
+ // Use constant size modifier to limit relative color percentage ranges.
+ rule.setContentWithTheme(modifier = Modifier.size(204.dp), composable = composable)
+ }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt
index c95871c..da576d4 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt
@@ -58,6 +58,8 @@
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
@@ -97,6 +99,10 @@
* Example of an [EdgeButton]:
*
* @sample androidx.wear.compose.material3.samples.EdgeButtonSample
+ *
+ * For a sample integrating with ScalingLazyColumn, see:
+ *
+ * @sample androidx.wear.compose.material3.samples.EdgeButtonListSample
* @param onClick Will be called when the user clicks the button
* @param modifier Modifier to be applied to the button. When animating the button to appear/
* disappear from the screen, a Modifier.height can be used to change the height of the component,
@@ -155,7 +161,7 @@
horizontalArrangement = Arrangement.Center,
modifier =
modifier
- .padding(bottom = BOTTOM_PADDING)
+ .padding(vertical = VERTICAL_PADDING)
.layout { measurable, constraints ->
// Compute the actual size of the button, and save it for later.
// We take the max width available, and the height is determined by the
@@ -233,6 +239,9 @@
provideScopeContent(
colors.contentColor(enabled = enabled),
MaterialTheme.typography.labelMedium,
+ TextOverflow.Ellipsis,
+ maxLines = 3, // TODO(): Change according to buttonHeight
+ TextAlign.Center,
content
)
)
@@ -264,7 +273,7 @@
internal class ShapeHelper(private val density: Density) {
private val extraSmallHeightPx =
with(density) { ButtonDefaults.EdgeButtonHeightExtraSmall.toPx() }
- private val bottomPaddingPx = with(density) { BOTTOM_PADDING.toPx() }
+ private val bottomPaddingPx = with(density) { VERTICAL_PADDING.toPx() }
private val extraSmallEllipsisHeightPx = with(density) { EXTRA_SMALL_ELLIPSIS_HEIGHT.toPx() }
private val targetSidePadding = with(density) { TARGET_SIDE_PADDING.toPx() }
@@ -439,5 +448,5 @@
// straight line parallel to the x axis.
private val TARGET_SIDE_PADDING = 20.dp
-// Padding between the Edge Button and the bottom of the screen
-private val BOTTOM_PADDING = 3.dp
+// Padding around the Edge Button on it's top and bottom.
+private val VERTICAL_PADDING = 3.dp
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ProgressIndicator.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ProgressIndicator.kt
index 0362590..7290311 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ProgressIndicator.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ProgressIndicator.kt
@@ -34,8 +34,6 @@
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material3.ProgressIndicatorDefaults.StartAngle
-import androidx.wear.compose.material3.ProgressIndicatorDefaults.StrokeWidth
import androidx.wear.compose.material3.tokens.ColorSchemeKeyTokens
import androidx.wear.compose.materialcore.toRadians
import kotlin.math.asin
@@ -85,10 +83,10 @@
fun CircularProgressIndicator(
progress: () -> Float,
modifier: Modifier = Modifier,
- startAngle: Float = StartAngle,
+ startAngle: Float = ProgressIndicatorDefaults.StartAngle,
endAngle: Float = startAngle,
colors: ProgressIndicatorColors = ProgressIndicatorDefaults.colors(),
- strokeWidth: Dp = StrokeWidth,
+ strokeWidth: Dp = ProgressIndicatorDefaults.StrokeWidth,
gapSize: Dp = ProgressIndicatorDefaults.gapSize(strokeWidth),
) {
val coercedProgress = { progress().coerceIn(0f, 1f) }
@@ -264,7 +262,7 @@
*
* If indicator gets too small, the circle that proportionally scales down is drawn instead.
*/
-private fun DrawScope.drawIndicatorSegment(
+internal fun DrawScope.drawIndicatorSegment(
startAngle: Float,
sweep: Float,
gapSweep: Float,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScreenScaffold.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScreenScaffold.kt
index 1842f55..c88b35f 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScreenScaffold.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScreenScaffold.kt
@@ -35,11 +35,21 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
import androidx.wear.compose.foundation.ActiveFocusListener
import androidx.wear.compose.foundation.ScrollInfoProvider
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.ScalingLazyListState
-import androidx.wear.compose.foundation.toScrollAwayInfoProvider
+import kotlin.math.roundToInt
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -53,6 +63,9 @@
* [ScreenScaffold] displays the [ScrollIndicator] at the center-end of the screen by default and
* coordinates showing/hiding [TimeText] and [ScrollIndicator] according to [scrollState].
*
+ * This version of [ScreenScaffold] has a special slot for a button at the bottom, that grows and
+ * shrinks to take the available space after the scrollable content.
+ *
* Example of using AppScaffold and ScreenScaffold:
*
* @sample androidx.wear.compose.material3.samples.ScaffoldSample
@@ -65,6 +78,9 @@
* @param scrollIndicator The [ScrollIndicator] to display on this screen, which is expected to be
* aligned to Center-End. It is recommended to use the Material3 [ScrollIndicator] which is
* provided by default. No scroll indicator is displayed if null is passed.
+ * @param bottomButton Optional slot for a Button (usually an [EdgeButton]) that takes the available
+ * space below a scrolling list. It will scale up and fade in when the user scrolls to the end of
+ * the list, and scale down and fade out as the user scrolls up.
* @param content The body content for this screen.
*/
@Composable
@@ -75,15 +91,27 @@
scrollIndicator: (@Composable BoxScope.() -> Unit)? = {
ScrollIndicator(scrollState, modifier = Modifier.align(Alignment.CenterEnd))
},
+ bottomButton: (@Composable BoxScope.() -> Unit)? = null,
content: @Composable BoxScope.() -> Unit,
) =
- ScreenScaffold(
- modifier,
- timeText,
- scrollState.toScrollAwayInfoProvider(),
- scrollIndicator,
- content
- )
+ if (bottomButton != null) {
+ ScreenScaffold(
+ bottomButton,
+ ScrollInfoProvider(scrollState),
+ modifier,
+ timeText,
+ scrollIndicator,
+ content
+ )
+ } else {
+ ScreenScaffold(
+ modifier,
+ timeText,
+ ScrollInfoProvider(scrollState),
+ scrollIndicator,
+ content
+ )
+ }
/**
* [ScreenScaffold] is one of the Wear Material3 scaffold components.
@@ -94,6 +122,9 @@
* [ScreenScaffold] displays the [ScrollIndicator] at the center-end of the screen by default and
* coordinates showing/hiding [TimeText] and [ScrollIndicator] according to [scrollState].
*
+ * This version of [ScreenScaffold] has a special slot for a button at the bottom, that grows and
+ * shrinks to take the available space after the scrollable content.
+ *
* Example of using AppScaffold and ScreenScaffold:
*
* @sample androidx.wear.compose.material3.samples.ScaffoldSample
@@ -106,25 +137,93 @@
* @param scrollIndicator The [ScrollIndicator] to display on this screen, which is expected to be
* aligned to Center-End. It is recommended to use the Material3 [ScrollIndicator] which is
* provided by default. No scroll indicator is displayed if null is passed.
+ * @param bottomButton Optional slot for a Button (usually an [EdgeButton]) that takes the available
+ * space below a scrolling list. It will scale up and fade in when the user scrolls to the end of
+ * the list, and scale down and fade out as the user scrolls up.
* @param content The body content for this screen.
*/
@Composable
fun ScreenScaffold(
scrollState: LazyListState,
modifier: Modifier = Modifier,
+ scrollIndicator: @Composable BoxScope.() -> Unit = {
+ ScrollIndicator(scrollState, modifier = Modifier.align(Alignment.CenterEnd))
+ },
+ timeText: (@Composable () -> Unit)? = null,
+ bottomButton: (@Composable BoxScope.() -> Unit)? = null,
+ content: @Composable BoxScope.() -> Unit,
+) =
+ if (bottomButton != null) {
+ ScreenScaffold(
+ bottomButton,
+ ScrollInfoProvider(scrollState),
+ modifier,
+ timeText,
+ scrollIndicator,
+ content
+ )
+ } else {
+ ScreenScaffold(
+ modifier,
+ timeText,
+ ScrollInfoProvider(scrollState),
+ scrollIndicator,
+ content
+ )
+ }
+
+/**
+ * [ScreenScaffold] is one of the Wear Material3 scaffold components.
+ *
+ * The scaffold components [AppScaffold] and [ScreenScaffold] lay out the structure of a screen and
+ * coordinate transitions of the [ScrollIndicator] and [TimeText] components.
+ *
+ * [ScreenScaffold] displays the [ScrollIndicator] at the center-end of the screen by default and
+ * coordinates showing/hiding [TimeText] and [ScrollIndicator] according to [scrollState].
+ *
+ * This version of [ScreenScaffold] has a special slot for a button at the bottom, that grows and
+ * shrinks to take the available space after the scrollable content.
+ *
+ * Example of using AppScaffold and ScreenScaffold:
+ *
+ * @sample androidx.wear.compose.material3.samples.ScaffoldSample
+ * @param scrollState The scroll state for a Column, used to drive screen transitions such as
+ * [TimeText] scroll away and showing/hiding [ScrollIndicator].
+ * @param bottomButton Optional slot for a Button (usually an [EdgeButton]) that takes the available
+ * space below a scrolling list. It will scale up and fade in when the user scrolls to the end of
+ * the list, and scale down and fade out as the user scrolls up.
+ * @param bottomButtonHeight the maximum height of the space taken by the bottom button.
+ * @param modifier The modifier for the screen scaffold.
+ * @param timeText Time text (both time and potentially status message) for this screen, if
+ * different to the time text at the [AppScaffold] level. When null, the time text from the
+ * [AppScaffold] is displayed for this screen.
+ * @param scrollIndicator The [ScrollIndicator] to display on this screen, which is expected to be
+ * aligned to Center-End. It is recommended to use the Material3 [ScrollIndicator] which is
+ * provided by default. No scroll indicator is displayed if null is passed.
+ * @param content The body content for this screen.
+ */
+@Composable
+fun ScreenScaffold(
+ scrollState: ScrollState,
+ bottomButton: @Composable BoxScope.() -> Unit,
+ bottomButtonHeight: Dp,
+ modifier: Modifier = Modifier,
timeText: (@Composable () -> Unit)? = null,
scrollIndicator: (@Composable BoxScope.() -> Unit)? = {
ScrollIndicator(scrollState, modifier = Modifier.align(Alignment.CenterEnd))
},
content: @Composable BoxScope.() -> Unit,
-) =
+) {
+ val bottomButtonHeightPx = with(LocalDensity.current) { bottomButtonHeight.toPx() }
ScreenScaffold(
+ bottomButton,
+ ScrollInfoProvider(scrollState, bottomButtonHeightPx),
modifier,
timeText,
- scrollState.toScrollAwayInfoProvider(),
scrollIndicator,
content
)
+}
/**
* [ScreenScaffold] is one of the Wear Material3 scaffold components.
@@ -158,13 +257,64 @@
ScrollIndicator(scrollState, modifier = Modifier.align(Alignment.CenterEnd))
},
content: @Composable BoxScope.() -> Unit,
+) = ScreenScaffold(modifier, timeText, ScrollInfoProvider(scrollState), scrollIndicator, content)
+
+/**
+ * [ScreenScaffold] is one of the Wear Material3 scaffold components.
+ *
+ * The scaffold components [AppScaffold] and [ScreenScaffold] lay out the structure of a screen and
+ * coordinate transitions of the [ScrollIndicator] and [TimeText] components.
+ *
+ * [ScreenScaffold] displays the [ScrollIndicator] at the center-end of the screen by default and
+ * coordinates showing/hiding [TimeText], [ScrollIndicator] and the bottom button according to a
+ * [scrollInfoProvider].
+ *
+ * This version of [ScreenScaffold] has a special slot for a button at the bottom, that grows and
+ * shrinks to take the available space after the scrollable content. In this overload, both
+ * bottomButton and scrollInfoProvider must be specified.
+ *
+ * Example of using AppScaffold and ScreenScaffold:
+ *
+ * @sample androidx.wear.compose.material3.samples.ScaffoldSample
+ * @param bottomButton slot for a Button (usually an [EdgeButton]) that takes the available space
+ * below a scrolling list. It will scale up and fade in when the user scrolls to the end of the
+ * list, and scale down and fade out as the user scrolls up.
+ * @param scrollInfoProvider Provider for scroll information used to scroll away screen elements
+ * such as [TimeText] and coordinate showing/hiding the [ScrollIndicator], this needs to be a
+ * [ScrollInfoProvider].
+ * @param modifier The modifier for the screen scaffold.
+ * @param timeText Time text (both time and potentially status message) for this screen, if
+ * different to the time text at the [AppScaffold] level. When null, the time text from the
+ * [AppScaffold] is displayed for this screen.
+ * @param scrollIndicator The [ScrollIndicator] to display on this screen, which is expected to be
+ * aligned to Center-End. It is recommended to use the Material3 [ScrollIndicator] which is
+ * provided by default. No scroll indicator is displayed if null is passed.
+ * @param content The body content for this screen.
+ */
+@Composable
+fun ScreenScaffold(
+ bottomButton: @Composable BoxScope.() -> Unit,
+ scrollInfoProvider: ScrollInfoProvider,
+ modifier: Modifier = Modifier,
+ timeText: (@Composable () -> Unit)? = null,
+ scrollIndicator: (@Composable BoxScope.() -> Unit)? = null,
+ content: @Composable BoxScope.() -> Unit,
) =
ScreenScaffold(
modifier,
timeText,
- scrollState.toScrollAwayInfoProvider(),
+ scrollInfoProvider,
scrollIndicator,
- content
+ content = {
+ content()
+ Box(
+ Modifier.align(Alignment.BottomCenter).dynamicHeight {
+ scrollInfoProvider.lastItemOffset.coerceAtLeast(0f)
+ },
+ contentAlignment = Alignment.BottomCenter,
+ content = bottomButton
+ )
+ }
)
/**
@@ -261,3 +411,54 @@
)
}
}
+
+// Sets the height that will be used down the line, using a state as parameter, to avoid
+// recompositions when the height changes.
+internal fun Modifier.dynamicHeight(heightState: () -> Float) =
+ this.then(DynamicHeightElement(heightState))
+
+// Following classes 'inspired' by 'WrapContentElement' / 'WrapContentNode'
+private class DynamicHeightElement(val heightState: () -> Float) :
+ ModifierNodeElement<DynamicHeightNode>() {
+ override fun create(): DynamicHeightNode = DynamicHeightNode(heightState)
+
+ override fun update(node: DynamicHeightNode) {
+ node.heightState = heightState
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "MyHeightElement"
+ }
+
+ override fun equals(other: Any?) =
+ other is DynamicHeightElement && heightState === other.heightState
+
+ override fun hashCode() = heightState.hashCode()
+}
+
+private class DynamicHeightNode(var heightState: () -> Float) :
+ LayoutModifierNode, Modifier.Node() {
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints
+ ): MeasureResult {
+ // Similar to .fillMaxWidth().height(heightState.value) but we observe the state in the
+ // measurement pass, not on Composition.
+ val height = heightState().roundToInt()
+
+ val wrappedConstraints =
+ Constraints(constraints.maxWidth, constraints.maxWidth, height, height)
+ val placeable = measurable.measure(wrappedConstraints)
+ // Report that we take the full space, and BottomCenter align the content.
+ val wrapperWidth = constraints.maxWidth
+ val wrapperHeight = constraints.maxHeight
+ return layout(wrapperWidth, wrapperHeight) {
+ val position =
+ IntOffset(
+ x = (wrapperWidth - placeable.width) / 2,
+ y = wrapperHeight - placeable.height
+ )
+ placeable.place(position)
+ }
+ }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScrollAway.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScrollAway.kt
index b561f85..7239dbe 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScrollAway.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ScrollAway.kt
@@ -33,7 +33,6 @@
import androidx.compose.ui.util.lerp
import androidx.wear.compose.foundation.ScrollInfoProvider
import androidx.wear.compose.foundation.lazy.ScalingLazyListState
-import androidx.wear.compose.foundation.toScrollAwayInfoProvider
import androidx.wear.compose.material3.tokens.MotionTokens
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -48,8 +47,8 @@
*
* @sample androidx.wear.compose.material3.samples.ScrollAwaySample
* @param scrollInfoProvider Used as the basis for the scroll-away implementation, based on the
- * state of the scrollable container. See [toScrollAwayInfoProvider] extension methods for
- * creating a ScrollAwayInfoProvider from common lists such as [ScalingLazyListState].
+ * state of the scrollable container. See [ScrollInfoProvider] methods for creating a
+ * ScrollInfoProvider from common lists such as [ScalingLazyListState].
* @param screenStage Function that returns the screen stage of the active screen. Scrolled away
* items are shown when the screen is new, then scrolled away or hidden when scrolling, and
* finally shown again when idle.
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SegmentedCircularProgressIndicator.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SegmentedCircularProgressIndicator.kt
new file mode 100644
index 0000000..edd8780
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SegmentedCircularProgressIndicator.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2024 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.wear.compose.material3
+
+import androidx.annotation.IntRange
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.unit.Dp
+import kotlin.math.asin
+import kotlin.math.floor
+import kotlin.math.min
+
+/**
+ * Material Design segmented circular progress indicator.
+ *
+ * A segmented variant of [CircularProgressIndicator] that is divided into equally sized segments.
+ *
+ * Example of [SegmentedCircularProgressIndicator] with progress value:
+ *
+ * @sample androidx.wear.compose.material3.samples.SegmentedProgressIndicatorSample
+ * @param segmentCount Number of equal segments that the progress indicator should be divided into.
+ * Has to be a number equal or greater to 1.
+ * @param progress The progress of this progress indicator where 0.0 represents no progress and 1.0
+ * represents completion. Values outside of this range are coerced into the range 0..1. The
+ * progress is applied to the entire [SegmentedCircularProgressIndicator] across all segments.
+ * @param modifier Modifier to be applied to the SegmentedCircularProgressIndicator.
+ * @param startAngle The starting position of the progress arc, measured clockwise in degrees (0
+ * to 360) from the 3 o'clock position. For example, 0 and 360 represent 3 o'clock, 90 and 180
+ * represent 6 o'clock and 9 o'clock respectively. Default is 270 degrees
+ * [ProgressIndicatorDefaults.StartAngle] (top of the screen).
+ * @param endAngle The ending position of the progress arc, measured clockwise in degrees (0 to 360)
+ * from the 3 o'clock position. For example, 0 and 360 represent 3 o'clock, 90 and 180 represent 6
+ * o'clock and 9 o'clock respectively. By default equal to [startAngle].
+ * @param colors [ProgressIndicatorColors] that will be used to resolve the indicator and track
+ * color for this progress indicator in different states.
+ * @param strokeWidth The stroke width for the progress indicator.
+ * @param gapSize The size of the gap between segments (in Dp).
+ */
+@Composable
+fun SegmentedCircularProgressIndicator(
+ @IntRange(from = 1) segmentCount: Int,
+ progress: () -> Float,
+ modifier: Modifier = Modifier,
+ startAngle: Float = ProgressIndicatorDefaults.StartAngle,
+ endAngle: Float = startAngle,
+ colors: ProgressIndicatorColors = ProgressIndicatorDefaults.colors(),
+ strokeWidth: Dp = ProgressIndicatorDefaults.StrokeWidth,
+ gapSize: Dp = ProgressIndicatorDefaults.gapSize(strokeWidth),
+) =
+ SegmentedCircularProgressIndicatorImpl(
+ segmentParams = SegmentParams.Progress(progress),
+ modifier = modifier,
+ segmentCount = segmentCount,
+ startAngle = startAngle,
+ endAngle = endAngle,
+ colors = colors,
+ strokeWidth = strokeWidth,
+ gapSize = gapSize,
+ )
+
+/**
+ * Material Design segmented circular progress indicator.
+ *
+ * A segmented variant of [CircularProgressIndicator] that is divided into equally sized segments.
+ * This overload of [SegmentedCircularProgressIndicator] allows for each segment to be individually
+ * indicated as completed, such as for showing activity for intervals within a longer period
+ *
+ * Example of [SegmentedCircularProgressIndicator] where the segments are turned on/off:
+ *
+ * @sample androidx.wear.compose.material3.samples.SegmentedProgressIndicatorOnOffSample
+ * @param segmentCount Number of equal segments that the progress indicator should be divided into.
+ * Has to be a number equal or greater to 1.
+ * @param completed A function that for each segment between 1..[segmentCount] returns true if this
+ * segment has been completed, and false if this segment has not been completed.
+ * @param modifier Modifier to be applied to the SegmentedCircularProgressIndicator.
+ * @param startAngle The starting position of the progress arc, measured clockwise in degrees (0
+ * to 360) from the 3 o'clock position. For example, 0 and 360 represent 3 o'clock, 90 and 180
+ * represent 6 o'clock and 9 o'clock respectively. Default is 270 degrees
+ * [ProgressIndicatorDefaults.StartAngle] (top of the screen).
+ * @param endAngle The ending position of the progress arc, measured clockwise in degrees (0 to 360)
+ * from the 3 o'clock position. For example, 0 and 360 represent 3 o'clock, 90 and 180 represent 6
+ * o'clock and 9 o'clock respectively. By default equal to [startAngle].
+ * @param colors [ProgressIndicatorColors] that will be used to resolve the indicator and track
+ * color for this progress indicator in different states.
+ * @param strokeWidth The stroke width for the progress indicator.
+ * @param gapSize The size of the gap between segments (in Dp).
+ */
+@Composable
+fun SegmentedCircularProgressIndicator(
+ @IntRange(from = 1) segmentCount: Int,
+ completed: (segmentIndex: Int) -> Boolean,
+ modifier: Modifier = Modifier,
+ startAngle: Float = ProgressIndicatorDefaults.StartAngle,
+ endAngle: Float = startAngle,
+ colors: ProgressIndicatorColors = ProgressIndicatorDefaults.colors(),
+ strokeWidth: Dp = ProgressIndicatorDefaults.StrokeWidth,
+ gapSize: Dp = ProgressIndicatorDefaults.gapSize(strokeWidth),
+) =
+ SegmentedCircularProgressIndicatorImpl(
+ segmentParams = SegmentParams.Completed(completed),
+ modifier = modifier,
+ segmentCount = segmentCount,
+ startAngle = startAngle,
+ endAngle = endAngle,
+ colors = colors,
+ strokeWidth = strokeWidth,
+ gapSize = gapSize,
+ )
+
+@Composable
+private fun SegmentedCircularProgressIndicatorImpl(
+ segmentParams: SegmentParams,
+ @IntRange(from = 1) segmentCount: Int,
+ modifier: Modifier,
+ startAngle: Float,
+ endAngle: Float,
+ colors: ProgressIndicatorColors,
+ strokeWidth: Dp,
+ gapSize: Dp,
+) {
+ Spacer(
+ modifier
+ .clearAndSetSemantics {}
+ .fillMaxSize()
+ .drawWithCache {
+ onDrawWithContent {
+ val fullSweep = 360f - ((startAngle - endAngle) % 360 + 360) % 360
+ val stroke = Stroke(width = strokeWidth.toPx(), cap = StrokeCap.Round)
+ val minSize = min(size.height, size.width)
+ // Sweep angle between two progress indicator segments.
+ val gapSweep =
+ asin((stroke.width + gapSize.toPx()) / (minSize - stroke.width))
+ .toDegrees() * 2f
+ val segmentSweepAngle =
+ if (segmentCount > 1) fullSweep / segmentCount - gapSweep else fullSweep
+
+ for (segment in 0 until segmentCount) {
+ val segmentStartAngle =
+ startAngle +
+ fullSweep * segment / segmentCount +
+ (if (segmentCount > 1) gapSweep / 2 else 0f)
+
+ when (segmentParams) {
+ is SegmentParams.Completed -> {
+ val color =
+ if (segmentParams.completed(segment)) colors.indicatorBrush
+ else colors.trackBrush
+
+ drawIndicatorSegment(
+ startAngle = segmentStartAngle,
+ sweep = segmentSweepAngle,
+ gapSweep = 0f,
+ brush = color,
+ stroke = stroke
+ )
+ }
+ is SegmentParams.Progress -> {
+ val progressInSegments =
+ segmentCount * segmentParams.progress().coerceIn(0f, 1f)
+
+ if (segment >= floor(progressInSegments)) {
+ drawIndicatorSegment(
+ startAngle = segmentStartAngle,
+ sweep = segmentSweepAngle,
+ gapSweep = 0f, // Overlay, no gap
+ brush = colors.trackBrush,
+ stroke = stroke
+ )
+ }
+ if (segment < progressInSegments) {
+ val progressSweepAngle =
+ segmentSweepAngle *
+ (progressInSegments - segment).coerceAtMost(1f)
+
+ drawIndicatorSegment(
+ startAngle = segmentStartAngle,
+ sweep = progressSweepAngle,
+ gapSweep = 0f, // Overlay, no gap
+ brush = colors.indicatorBrush,
+ stroke = stroke
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+}
+
+private sealed interface SegmentParams {
+ data class Completed(val completed: (segmentIndex: Int) -> Boolean) : SegmentParams
+
+ data class Progress(val progress: () -> Float) : SegmentParams
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwitchButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwitchButton.kt
index eddc0b9..e5ca690 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwitchButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SwitchButton.kt
@@ -23,24 +23,35 @@
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxHeight
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.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
@@ -52,9 +63,11 @@
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.wear.compose.material3.tokens.MotionTokens
+import androidx.wear.compose.material3.tokens.ShapeTokens
import androidx.wear.compose.material3.tokens.SplitSwitchButtonTokens
import androidx.wear.compose.material3.tokens.SwitchButtonTokens
import androidx.wear.compose.materialcore.SelectionStage
@@ -253,21 +266,85 @@
contentPadding: PaddingValues = SwitchButtonDefaults.ContentPadding,
secondaryLabel: @Composable (RowScope.() -> Unit)? = null,
label: @Composable RowScope.() -> Unit
-) =
- androidx.wear.compose.materialcore.SplitToggleButton(
- checked = checked,
- onCheckedChange = onCheckedChange,
- label =
- provideScopeContent(
- contentColor = colors.contentColor(enabled = enabled, checked = checked),
- textStyle = SplitSwitchButtonTokens.LabelFont.value,
- overflow = TextOverflow.Ellipsis,
- maxLines = 3,
- textAlign = TextAlign.Start,
- content = label
- ),
- onClick = onContainerClick,
- toggleControl = {
+) {
+ val containerColor = colors.containerColor(enabled, checked).value
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ modifier
+ .defaultMinSize(minHeight = MIN_HEIGHT)
+ .height(IntrinsicSize.Min)
+ .width(IntrinsicSize.Max)
+ .clip(shape = shape)
+ ) {
+ Row(
+ modifier =
+ Modifier.clickable(
+ enabled = enabled,
+ onClick = onContainerClick,
+ indication = ripple(),
+ interactionSource = containerInteractionSource,
+ onClickLabel = containerClickLabel,
+ )
+ .semantics { role = Role.Button }
+ .fillMaxHeight()
+ .clip(SPLIT_SECTIONS_SHAPE)
+ .background(containerColor)
+ .padding(contentPadding)
+ .weight(1.0f),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Labels(
+ label =
+ provideScopeContent(
+ contentColor = colors.contentColor(enabled = enabled, checked = checked),
+ textStyle = SplitSwitchButtonTokens.LabelFont.value,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 3,
+ textAlign = TextAlign.Start,
+ content = label
+ ),
+ secondaryLabel =
+ provideNullableScopeContent(
+ contentColor =
+ colors.secondaryContentColor(enabled = enabled, checked = checked),
+ textStyle = SplitSwitchButtonTokens.SecondaryLabelFont.value,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 2,
+ textAlign = TextAlign.Start,
+ content = secondaryLabel
+ ),
+ spacerSize = SwitchButtonDefaults.LabelSpacerSize
+ )
+ }
+
+ Spacer(modifier = Modifier.size(2.dp))
+
+ val splitBackground = colors.splitContainerColor(enabled, checked).value
+ Box(
+ modifier =
+ Modifier.toggleable(
+ enabled = enabled,
+ value = checked,
+ onValueChange = onCheckedChange,
+ indication = ripple(),
+ interactionSource = toggleInteractionSource
+ )
+ .fillMaxHeight()
+ .clip(SPLIT_SECTIONS_SHAPE)
+ .background(containerColor)
+ .drawWithCache {
+ onDrawWithContent {
+ drawRect(color = splitBackground)
+ drawContent()
+ }
+ }
+ .align(Alignment.CenterVertically)
+ .width(SPLIT_WIDTH)
+ .wrapContentHeight(align = Alignment.CenterVertically)
+ .padding(contentPadding)
+ ) {
Switch(
checked = checked,
enabled = enabled,
@@ -290,33 +367,9 @@
colors.trackBorderColor(enabled = enabled, checked = checked)
}
)
- },
- selectionControl = null,
- modifier = modifier.defaultMinSize(minHeight = MIN_HEIGHT).height(IntrinsicSize.Min),
- secondaryLabel =
- provideNullableScopeContent(
- contentColor = colors.secondaryContentColor(enabled = enabled, checked = checked),
- textStyle = SplitSwitchButtonTokens.SecondaryLabelFont.value,
- overflow = TextOverflow.Ellipsis,
- maxLines = 2,
- textAlign = TextAlign.Start,
- content = secondaryLabel
- ),
- backgroundColor = { isEnabled, isChecked ->
- colors.containerColor(enabled = isEnabled, checked = isChecked)
- },
- splitBackgroundColor = { isEnabled, isChecked ->
- colors.splitContainerColor(enabled = isEnabled, checked = isChecked)
- },
- enabled = enabled,
- checkedInteractionSource = toggleInteractionSource,
- clickInteractionSource = containerInteractionSource,
- onClickLabel = containerClickLabel,
- contentPadding = contentPadding,
- shape = shape,
- labelSpacerSize = SwitchButtonDefaults.LabelSpacerSize,
- ripple = ripple()
- )
+ }
+ }
+}
/** Contains the default values used by [SwitchButton]s and [SplitSwitchButton]s */
object SwitchButtonDefaults {
@@ -1744,6 +1797,21 @@
)
}
+@Composable
+private fun RowScope.Labels(
+ label: @Composable RowScope.() -> Unit,
+ secondaryLabel: @Composable (RowScope.() -> Unit)?,
+ spacerSize: Dp
+) {
+ Column(modifier = Modifier.weight(1.0f)) {
+ Row(content = label)
+ if (secondaryLabel != null) {
+ Spacer(modifier = Modifier.size(spacerSize))
+ Row(content = secondaryLabel)
+ }
+ }
+}
+
private val SWITCH_WIDTH = 32.dp
private val SWITCH_OUTER_HEIGHT = 24.dp
private val SWITCH_INNER_HEIGHT = 22.dp
@@ -1754,6 +1822,9 @@
private val ICON_SPACING = 6.dp
private val MIN_HEIGHT = 52.dp
+private val SPLIT_WIDTH = 60.dp
+private val SPLIT_SECTIONS_SHAPE = ShapeTokens.CornerExtraSmall
+
private val COLOR_ANIMATION_SPEC: AnimationSpec<Color> =
tween(MotionTokens.DurationMedium1, 0, MotionTokens.EasingStandardDecelerate)
private val SWITCH_PROGRESS_ANIMATION_SPEC: TweenSpec<Float> =
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index e43fb98..0da615f 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -25,8 +25,8 @@
defaultConfig {
applicationId "androidx.wear.compose.integration.demos"
minSdk 25
- versionCode 32
- versionName "1.32"
+ versionCode 33
+ versionName "1.33"
}
buildTypes {
diff --git a/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt b/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
index 2d88c40..bfbf053 100644
--- a/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
+++ b/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
@@ -24,6 +24,7 @@
import androidx.wear.watchface.complications.IllegalNodeException
import androidx.wear.watchface.complications.iterate
import androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption
+import androidx.wear.watchface.style.UserStyleSetting.LargeCustomValueUserStyleSetting.Companion.CUSTOM_VALUE_USER_STYLE_SETTING_ID
import androidx.wear.watchface.style.UserStyleSetting.Option
import androidx.wear.watchface.style.data.UserStyleSchemaWireFormat
import androidx.wear.watchface.style.data.UserStyleWireFormat
@@ -379,10 +380,20 @@
"{" +
userStyleMap.entries.joinToString(
transform = {
- try {
- it.key + "=" + it.value.decodeToString()
- } catch (e: Exception) {
- it.key + "=" + it.value
+ when (it.key) {
+ /**
+ * For CustomValueUserStyleSetting and LargeCustomValueUserStyleSetting, we
+ * display only the length of the value. These style settings is always use
+ * the same key (CustomValue).
+ */
+ CUSTOM_VALUE_USER_STYLE_SETTING_ID ->
+ it.key + "=[binary data, length: ${it.value.size}]"
+ else ->
+ try {
+ it.key + "=" + it.value.decodeToString()
+ } catch (e: Exception) {
+ it.key + "=" + it.value
+ }
}
}
) +
diff --git a/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt b/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
index 0f7ca2d..847a4ee 100644
--- a/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
+++ b/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
@@ -2896,6 +2896,8 @@
override fun write(dos: DataOutputStream) {
dos.write(id.value)
}
+
+ override fun toString(): String = "[binary data, length: ${customValue.size}]"
}
override fun getOptionForId(optionId: Option.Id): Option =
diff --git a/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt b/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt
index 3029d3e..d4014c6 100644
--- a/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt
+++ b/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt
@@ -600,7 +600,8 @@
assertThat(gothicStyleOption.toString()).isEqualTo("gothic_style")
assertThat(DoubleRangeUserStyleSetting.DoubleRangeOption(12.3).toString()).isEqualTo("12.3")
assertThat(LongRangeUserStyleSetting.LongRangeOption(123).toString()).isEqualTo("123")
- assertThat(CustomValueOption("test".encodeToByteArray()).toString()).isEqualTo("test")
+ assertThat(CustomValueOption("test".encodeToByteArray()).toString())
+ .isEqualTo("[binary data, length: 4]")
}
@Test
diff --git a/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt b/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt
index c98331c..ffb9f38 100644
--- a/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt
+++ b/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt
@@ -738,7 +738,10 @@
UserStyleSchema(listOf(styleSetting1, styleSetting2, styleSetting3, styleSetting4))
assertThat(schema.toString())
- .isEqualTo("[{id1 : 1, 2}, {id2 : 3, 4}, {id3 : true, false}, {CustomValue : default}]")
+ .isEqualTo(
+ "[{id1 : 1, 2}, {id2 : 3, 4}, {id3 : true, false}, " +
+ "{CustomValue : [binary data, length: 7]}]"
+ )
}
@Ignore
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 0e19a63..0725ecc 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -1739,9 +1739,7 @@
synchronized(lock) {
val complications = overriddenComplications ?: HashMap(complicationsFlow.value)
for ((frozenSlot, previewData) in editedComplicationPreviewData) {
- if (
- complicationsFlow.value[frozenSlot]!!.dataSource != previewData.dataSource
- ) {
+ if (complicationsFlow.value[frozenSlot]?.dataSource != previewData.dataSource) {
complications[frozenSlot] = EmptyComplicationData()
}
}