Merge "Use immutable collection for KSP" into androidx-main
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecTest.java
index 0a0ce31..c1bb7af 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecTest.java
@@ -22,6 +22,9 @@
import org.junit.Test;
+import java.util.List;
+import java.util.Map;
+
public class SearchSpecTest {
@Test
public void testGetBundle() {
@@ -52,4 +55,22 @@
assertThat(bundle.getInt(SearchSpec.RANKING_STRATEGY_FIELD))
.isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
}
+
+ @Test
+ public void testGetProjectionTypePropertyMasks() {
+ SearchSpec searchSpec = new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+ .addProjectionTypePropertyPaths("TypeA", "field1", "field2.subfield2")
+ .addProjectionTypePropertyPaths("TypeB", "field7")
+ .addProjectionTypePropertyPaths("TypeC")
+ .build();
+
+ Map<String, List<String>> typePropertyPathMap =
+ searchSpec.getProjectionTypePropertyPaths();
+ assertThat(typePropertyPathMap.keySet())
+ .containsExactly("TypeA", "TypeB", "TypeC");
+ assertThat(typePropertyPathMap.get("TypeA")).containsExactly("field1", "field2.subfield2");
+ assertThat(typePropertyPathMap.get("TypeB")).containsExactly("field7");
+ assertThat(typePropertyPathMap.get("TypeC")).isEmpty();
+ }
}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/util/AppSearchTestUtils.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/util/AppSearchTestUtils.java
index a6d768a..e9cade2 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/util/AppSearchTestUtils.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/util/AppSearchTestUtils.java
@@ -17,6 +17,7 @@
package androidx.appsearch.app.util;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import androidx.appsearch.app.AppSearchBatchResult;
import androidx.appsearch.app.AppSearchSession;
@@ -25,8 +26,6 @@
import androidx.appsearch.app.SearchResult;
import androidx.appsearch.app.SearchResults;
-import junit.framework.AssertionFailedError;
-
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
@@ -36,9 +35,8 @@
public static <K, V> AppSearchBatchResult<K, V> checkIsBatchResultSuccess(
Future<AppSearchBatchResult<K, V>> future) throws Exception {
AppSearchBatchResult<K, V> result = future.get();
- if (!result.isSuccess()) {
- throw new AssertionFailedError("AppSearchBatchResult not successful: " + result);
- }
+ assertWithMessage("AppSearchBatchResult not successful: " + result)
+ .that(result.isSuccess()).isTrue();
return result;
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
index fc0f6e2..f3c6a54 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
@@ -25,6 +25,7 @@
import androidx.annotation.RestrictTo;
import androidx.appsearch.exceptions.AppSearchException;
import androidx.appsearch.exceptions.IllegalSearchSpecException;
+import androidx.collection.ArrayMap;
import androidx.core.util.Preconditions;
import java.lang.annotation.Retention;
@@ -34,6 +35,8 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* This class represents the specification logic for AppSearch. It can be used to set the type of
@@ -41,6 +44,15 @@
*/
// TODO(sidchhabra) : AddResultSpec fields for Snippets etc.
public final class SearchSpec {
+ /**
+ * Schema type to be used in {@link SearchSpec.Builder#addProjectionTypePropertyPath} to apply
+ * property paths to all results, excepting any types that have had their own, specific
+ * property paths set.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
+
static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
static final String SCHEMA_TYPE_FIELD = "schemaType";
static final String NAMESPACE_FIELD = "namespace";
@@ -50,6 +62,7 @@
static final String SNIPPET_COUNT_FIELD = "snippetCount";
static final String SNIPPET_COUNT_PER_PROPERTY_FIELD = "snippetCountPerProperty";
static final String MAX_SNIPPET_FIELD = "maxSnippet";
+ static final String PROJECTION_TYPE_PROPERTY_PATHS_FIELD = "projectionTypeFieldMasks";
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -209,12 +222,36 @@
return mBundle.getInt(MAX_SNIPPET_FIELD);
}
+ /**
+ * Returns a map from schema type to property paths to be used for projection.
+ *
+ * <p>If the map is empty, then all properties will be retrieved for all results.
+ *
+ * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned
+ * by this function, rather than calling it multiple times.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @NonNull
+ public Map<String, List<String>> getProjectionTypePropertyPaths() {
+ Bundle typePropertyPathsBundle =
+ mBundle.getBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD);
+ Set<String> schemaTypes = typePropertyPathsBundle.keySet();
+ Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemaTypes.size());
+ for (String schemaType : schemaTypes) {
+ typePropertyPathsMap.put(schemaType,
+ typePropertyPathsBundle.getStringArrayList(schemaType));
+ }
+ return typePropertyPathsMap;
+ }
+
/** Builder for {@link SearchSpec objects}. */
public static final class Builder {
private final Bundle mBundle;
private final ArrayList<String> mSchemaTypes = new ArrayList<>();
private final ArrayList<String> mNamespaces = new ArrayList<>();
+ private final Bundle mProjectionTypePropertyMasks = new Bundle();
private boolean mBuilt = false;
/** Creates a new {@link SearchSpec.Builder}. */
@@ -424,6 +461,108 @@
return this;
}
+ /**
+ * Adds property paths for the specified type to be used for projection. If property
+ * paths are added for a type, then only the properties referred to will be retrieved for
+ * results of that type. If a property path that is specified isn't present in a result,
+ * it will be ignored for that result. Property paths cannot be null.
+ *
+ * <p>If no property paths are added for a particular type, then all properties of
+ * results of that type will be retrieved.
+ *
+ * <p>If property path is added for the
+ * {@link SearchSpec#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will
+ * apply to all results, excepting any types that have their own, specific property paths
+ * set.
+ *
+ * <p>Suppose the following document is in the index.
+ * <pre>{@code
+ * Email: Document {
+ * sender: Document {
+ * name: "Mr. Person"
+ * email: "mrperson123@google.com"
+ * }
+ * recipients: [
+ * Document {
+ * name: "John Doe"
+ * email: "johndoe123@google.com"
+ * }
+ * Document {
+ * name: "Jane Doe"
+ * email: "janedoe123@google.com"
+ * }
+ * ]
+ * subject: "IMPORTANT"
+ * body: "Limited time offer!"
+ * }
+ * }</pre>
+ *
+ * <p>Then, suppose that a query for "important" is issued with the following projection
+ * type property paths:
+ * <pre>{@code
+ * {schemaType: "Email", ["subject", "sender.name", "recipients.name"]}
+ * }</pre>
+ *
+ * <p>The above document will be returned as:
+ * <pre>{@code
+ * Email: Document {
+ * sender: Document {
+ * name: "Mr. Body"
+ * }
+ * recipients: [
+ * Document {
+ * name: "John Doe"
+ * }
+ * Document {
+ * name: "Jane Doe"
+ * }
+ * ]
+ * subject: "IMPORTANT"
+ * }
+ * }</pre>
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @NonNull
+ public SearchSpec.Builder addProjectionTypePropertyPaths(
+ @NonNull String schemaType, @NonNull String... propertyPaths) {
+ Preconditions.checkNotNull(propertyPaths);
+ return addProjectionTypePropertyPaths(schemaType, Arrays.asList(propertyPaths));
+ }
+
+ /**
+ * Adds property paths for the specified type to be used for projection. If property
+ * paths are added for a type, then only the properties referred to will be retrieved for
+ * results of that type. If a property path that is specified isn't present in a result,
+ * it will be ignored for that result. Property paths cannot be null.
+ *
+ * <p>If no property paths are added for a particular type, then all properties of
+ * results of that type will be retrieved.
+ *
+ * <p>If property path is added for the
+ * {@link SearchSpec#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will
+ * apply to all results, excepting any types that have their own, specific property paths
+ * set.
+ *
+ * {@see SearchSpec.Builder#addProjectionTypePropertyPath(String, String...)}
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @NonNull
+ public SearchSpec.Builder addProjectionTypePropertyPaths(
+ @NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(schemaType);
+ Preconditions.checkNotNull(propertyPaths);
+ ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
+ for (String propertyPath : propertyPaths) {
+ Preconditions.checkNotNull(propertyPath);
+ propertyPathsArrayList.add(propertyPath);
+ }
+ mProjectionTypePropertyMasks.putStringArrayList(schemaType, propertyPathsArrayList);
+ return this;
+ }
+
/**
* Constructs a new {@link SearchSpec} from the contents of this builder.
*
@@ -437,6 +576,7 @@
}
mBundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
mBundle.putStringArrayList(SCHEMA_TYPE_FIELD, mSchemaTypes);
+ mBundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks);
mBuilt = true;
return new SearchSpec(mBundle);
}
diff --git a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/LocalStorageTest.java b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/LocalStorageTest.java
index cfc1a31..254f8f7 100644
--- a/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/LocalStorageTest.java
+++ b/appsearch/local-storage/src/androidTest/java/androidx/appsearch/localstorage/LocalStorageTest.java
@@ -27,15 +27,15 @@
public class LocalStorageTest {
@Test
public void testSameInstance() throws Exception {
- LocalStorage b1 = LocalStorage.getInstance(ApplicationProvider.getApplicationContext())
- .get();
- LocalStorage b2 = LocalStorage.getInstance(ApplicationProvider.getApplicationContext())
- .get();
+ LocalStorage b1 =
+ LocalStorage.getOrCreateInstance(ApplicationProvider.getApplicationContext());
+ LocalStorage b2 =
+ LocalStorage.getOrCreateInstance(ApplicationProvider.getApplicationContext());
assertThat(b1).isSameInstanceAs(b2);
}
@Test
- public void testDatabaseName() throws Exception {
+ public void testDatabaseName() {
// Test special character can present in database name. When a special character is banned
// in database name, add checker in SearchContext.Builder and reflect it in java doc.
LocalStorage.SearchContext.Builder contextBuilder =
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
index adf4e60..e06f75d 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
@@ -18,12 +18,13 @@
import android.content.Context;
-import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
import androidx.appsearch.app.AppSearchSession;
import androidx.appsearch.app.GlobalSearchSession;
+import androidx.appsearch.exceptions.AppSearchException;
import androidx.appsearch.localstorage.util.FutureUtil;
import androidx.core.util.Preconditions;
@@ -54,7 +55,7 @@
@VisibleForTesting
public static final String DEFAULT_DATABASE_NAME = "";
- private static volatile ListenableFuture<LocalStorage> sInstance;
+ private static final String ICING_LIB_ROOT_DIR = "appsearch";
/** Contains information about how to create the search session. */
public static final class SearchContext {
@@ -156,14 +157,10 @@
// execute() won't return anything, we will hang forever waiting for the execution.
// AppSearch multi-thread execution is guarded by Read & Write Lock in AppSearchImpl, all
// mutate requests will need to gain write lock and query requests need to gain read lock.
- private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+ private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();
+ private static volatile LocalStorage sInstance;
- private static final String ICING_LIB_ROOT_DIR = "appsearch";
-
- // Package-level visibility to allow SearchResultsImpl to access it without a synthetic
- // accessor.
- volatile AppSearchImpl mAppSearchImpl;
-
+ private final AppSearchImpl mAppSearchImpl;
/**
* Opens a new {@link AppSearchSession} on this storage.
@@ -178,8 +175,10 @@
public static ListenableFuture<AppSearchSession> createSearchSession(
@NonNull SearchContext context) {
Preconditions.checkNotNull(context);
- ListenableFuture<LocalStorage> instFuture = getInstance(context.mContext);
- return FutureUtil.map(instFuture, (instance) -> instance.doCreateSearchSession(context));
+ return FutureUtil.execute(EXECUTOR_SERVICE, () -> {
+ LocalStorage instance = getOrCreateInstance(context.mContext);
+ return instance.doCreateSearchSession(context);
+ });
}
/**
@@ -196,8 +195,10 @@
public static ListenableFuture<GlobalSearchSession> createGlobalSearchSession(
@NonNull GlobalSearchContext context) {
Preconditions.checkNotNull(context);
- ListenableFuture<LocalStorage> instFuture = getInstance(context.mContext);
- return FutureUtil.map(instFuture, LocalStorage::doCreateGlobalSearchSession);
+ return FutureUtil.execute(EXECUTOR_SERVICE, () -> {
+ LocalStorage instance = getOrCreateInstance(context.mContext);
+ return instance.doCreateGlobalSearchSession();
+ });
}
/**
@@ -207,39 +208,35 @@
* {@code context}.
*/
@NonNull
+ @WorkerThread
@VisibleForTesting
- static ListenableFuture<LocalStorage> getInstance(@NonNull Context context) {
+ static LocalStorage getOrCreateInstance(@NonNull Context context) throws AppSearchException {
Preconditions.checkNotNull(context);
if (sInstance == null) {
synchronized (LocalStorage.class) {
if (sInstance == null) {
- sInstance = new LocalStorage().initialize(context);
+ sInstance = new LocalStorage(context);
}
}
}
return sInstance;
}
- private LocalStorage() {}
-
- // NOTE: No instance of this class should be created or returned except via initialize().
- // Once the ListenableFuture returned here is populated, the class is ready to use.
- @GuardedBy("LocalStorage.class")
- private ListenableFuture<LocalStorage> initialize(@NonNull Context context) {
+ @WorkerThread
+ private LocalStorage(@NonNull Context context) throws AppSearchException {
Preconditions.checkNotNull(context);
- return FutureUtil.execute(mExecutorService, () -> {
- File icingDir = new File(context.getFilesDir(), ICING_LIB_ROOT_DIR);
- mAppSearchImpl = AppSearchImpl.create(icingDir);
- return this;
- });
+ File icingDir = new File(context.getFilesDir(), ICING_LIB_ROOT_DIR);
+ mAppSearchImpl = AppSearchImpl.create(icingDir);
}
- AppSearchSession doCreateSearchSession(@NonNull SearchContext context) {
- return new SearchSessionImpl(mAppSearchImpl, mExecutorService,
+ @NonNull
+ private AppSearchSession doCreateSearchSession(@NonNull SearchContext context) {
+ return new SearchSessionImpl(mAppSearchImpl, EXECUTOR_SERVICE,
context.mContext.getPackageName(), context.mDatabaseName);
}
- GlobalSearchSession doCreateGlobalSearchSession() {
- return new GlobalSearchSessionImpl(mAppSearchImpl, mExecutorService);
+ @NonNull
+ private GlobalSearchSession doCreateGlobalSearchSession() {
+ return new GlobalSearchSessionImpl(mAppSearchImpl, EXECUTOR_SERVICE);
}
}
diff --git a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/util/FutureUtil.java b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/util/FutureUtil.java
index 5992050..99f5ae1 100644
--- a/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/util/FutureUtil.java
+++ b/appsearch/local-storage/src/main/java/androidx/appsearch/localstorage/util/FutureUtil.java
@@ -18,18 +18,13 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
-import androidx.arch.core.util.Function;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.core.util.Preconditions;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
/**
* Utilities for working with {@link com.google.common.util.concurrent.ListenableFuture}.
@@ -59,51 +54,4 @@
});
return future;
}
-
- /**
- * Returns a new {@link ListenableFuture} by applying the given lambda to the result of the old
- * future.
- *
- * <p>The lambda is applied as part of the get() call and its result is not cached.
- */
- @NonNull
- public static <I, O> ListenableFuture<O> map(
- @NonNull ListenableFuture<I> inputFuture, @NonNull Function<I, O> lambda) {
- Preconditions.checkNotNull(inputFuture);
- Preconditions.checkNotNull(lambda);
- return new ListenableFuture<O>() {
- @Override
- public void addListener(Runnable listener, Executor executor) {
- inputFuture.addListener(listener, executor);
- }
-
- @Override
- public boolean cancel(boolean mayInterruptIfRunning) {
- return inputFuture.cancel(mayInterruptIfRunning);
- }
-
- @Override
- public boolean isCancelled() {
- return inputFuture.isCancelled();
- }
-
- @Override
- public boolean isDone() {
- return inputFuture.isDone();
- }
-
- @Override
- public O get() throws ExecutionException, InterruptedException {
- I input = inputFuture.get();
- return lambda.apply(input);
- }
-
- @Override
- public O get(long timeout, TimeUnit unit)
- throws ExecutionException, InterruptedException, TimeoutException {
- I input = inputFuture.get(timeout, unit);
- return lambda.apply(input);
- }
- };
- }
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 046f1ed..382a1fb 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -42,7 +42,7 @@
val CAMERA_VIEW = Version("1.0.0-alpha20")
val CARDVIEW = Version("1.1.0-alpha01")
val CAR_APP = Version("1.0.0-alpha01")
- val COLLECTION = Version("1.2.0-alpha01")
+ val COLLECTION = Version("1.2.0-alpha02")
val CONTENTPAGER = Version("1.1.0-alpha01")
val COMPOSE = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.0.0-alpha10")
val COORDINATORLAYOUT = Version("1.2.0-alpha01")
@@ -124,16 +124,16 @@
val VERSIONED_PARCELABLE = Version("1.2.0-alpha01")
val VIEWPAGER = Version("1.1.0-alpha01")
val VIEWPAGER2 = Version("1.1.0-alpha02")
- val WEAR = Version("1.2.0-alpha04")
- val WEAR_COMPLICATIONS = Version("1.0.0-alpha04")
+ val WEAR = Version("1.2.0-alpha05")
+ val WEAR_COMPLICATIONS = Version("1.0.0-alpha05")
val WEAR_INPUT = Version("1.1.0-alpha01")
val WEAR_REMOTE_INTERACTIONS = Version("1.0.0-alpha01")
val WEAR_TILES = Version("1.0.0-alpha01")
val WEAR_TILES_DATA = WEAR_TILES
- val WEAR_WATCHFACE = Version("1.0.0-alpha04")
- val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha04")
- val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha04")
- val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha04")
+ val WEAR_WATCHFACE = Version("1.0.0-alpha05")
+ val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha05")
+ val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha05")
+ val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha05")
val WEBKIT = Version("1.4.0-beta01")
val WINDOW = Version("1.0.0-alpha02")
val WINDOW_EXTENSIONS = Version("1.0.0-alpha01")
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
index b16264b..10e7b5c 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
@@ -255,9 +255,9 @@
* applied or the frame number at which the method returned early because either frame limit
* or time limit was reached.
*/
- public fun lock3AForCapture(
+ public suspend fun lock3AForCapture(
frameLimit: Int = DEFAULT_FRAME_LIMIT,
- timeLimitMs: Int = DEFAULT_TIME_LIMIT_MS
+ timeLimitNs: Long = DEFAULT_TIME_LIMIT_NS
): Deferred<Result3A>
/**
@@ -268,6 +268,6 @@
* This method brings focus and exposure back to normal after high quality image captures
* using [lock3AForCapture] method.
*/
- public fun unlock3APostCapture(): Deferred<FrameNumber>
+ public suspend fun unlock3APostCapture(): Deferred<Result3A>
}
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt
index 7fd8ce5..32710c3 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt
@@ -121,15 +121,15 @@
throw UnsupportedOperationException()
}
- override fun lock3AForCapture(
+ override suspend fun lock3AForCapture(
frameLimit: Int,
- timeLimitMs: Int
+ timeLimitNs: Long
): Deferred<Result3A> {
- TODO("Implement lock3AForCapture")
+ return controller3A.lock3AForCapture(frameLimit, timeLimitNs)
}
- override fun unlock3APostCapture(): Deferred<FrameNumber> {
- TODO("Implement unlock3APostCapture")
+ override suspend fun unlock3APostCapture(): Deferred<Result3A> {
+ return controller3A.unlock3APostCapture()
}
override fun toString(): String = "CameraGraph.Session-$debugId"
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Controller3A.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Controller3A.kt
index 4b71c56..e5d67a5 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Controller3A.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Controller3A.kt
@@ -16,13 +16,18 @@
package androidx.camera.camera2.pipe.impl
+import android.hardware.camera2.CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START
import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER_CANCEL
import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER_START
import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureRequest.CONTROL_AE_LOCK
+import android.hardware.camera2.CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER
import android.hardware.camera2.CaptureRequest.CONTROL_AF_TRIGGER
import android.hardware.camera2.CaptureResult
import android.hardware.camera2.params.MeteringRectangle
+import android.os.Build
import androidx.annotation.GuardedBy
+import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.AeMode
import androidx.camera.camera2.pipe.AfMode
import androidx.camera.camera2.pipe.AwbMode
@@ -78,6 +83,12 @@
CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
)
+ private val aePostPrecaptureStateList = listOf(
+ CaptureResult.CONTROL_AE_STATE_CONVERGED,
+ CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED,
+ CaptureResult.CONTROL_AE_STATE_LOCKED
+ )
+
val parameterForAfTriggerStart = mapOf<CaptureRequest.Key<*>, Any>(
CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_START
)
@@ -86,6 +97,11 @@
CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_CANCEL
)
+ private val parametersForAePrecaptureAndAfTrigger = mapOf<CaptureRequest.Key<*>, Any>(
+ CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_START,
+ CONTROL_AE_PRECAPTURE_TRIGGER to CONTROL_AE_PRECAPTURE_TRIGGER_START
+ )
+
private val result3ASubmitFailed = Result3A(FRAME_NUMBER_INVALID, Status3A.SUBMIT_FAILED)
private val aeUnlockedStateList = listOf(
@@ -325,6 +341,111 @@
return listener.getDeferredResult()
}
+ suspend fun lock3AForCapture(
+ frameLimit: Int = CameraGraph.DEFAULT_FRAME_LIMIT,
+ timeLimitNs: Long = CameraGraph.DEFAULT_TIME_LIMIT_NS
+ ): Deferred<Result3A> {
+ val listener = Result3AStateListenerImpl(
+ mapOf<CaptureResult.Key<*>, List<Any>>(
+ CaptureResult.CONTROL_AE_STATE to aePostPrecaptureStateList,
+ CaptureResult.CONTROL_AF_STATE to afLockedStateList
+ ),
+ frameLimit,
+ timeLimitNs
+ )
+ graphListener3A.addListener(listener)
+
+ debug { "lock3AForCapture - sending a request to trigger ae precapture metering and af." }
+ if (!graphProcessor.submit(parametersForAePrecaptureAndAfTrigger)) {
+ debug {
+ "lock3AForCapture - request to trigger ae precapture metering and af failed, " +
+ "returning early."
+ }
+ graphListener3A.removeListener(listener)
+ return CompletableDeferred(result3ASubmitFailed)
+ }
+
+ graphProcessor.invalidate()
+ return listener.getDeferredResult()
+ }
+
+ suspend fun unlock3APostCapture(): Deferred<Result3A> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return unlock3APostCaptureAndroidMAndAbove()
+ }
+ return unlock3APostCaptureAndroidLAndBelow()
+ }
+
+ /**
+ * For api level below 23, to resume the normal scan of ae after precapture metering
+ * sequence, we have to first send a request with ae lock = true and then a request with ae
+ * lock = false. REF :
+ * https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ */
+ private suspend fun unlock3APostCaptureAndroidLAndBelow(): Deferred<Result3A> {
+ debug { "unlock3AForCapture - sending a request to cancel af and turn on ae." }
+ if (!graphProcessor.submit(
+ mapOf(
+ CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_CANCEL,
+ CONTROL_AE_LOCK to true
+ )
+ )
+ ) {
+ debug { "unlock3AForCapture - request to cancel af and lock ae as failed." }
+ return CompletableDeferred(result3ASubmitFailed)
+ }
+
+ // Listener to monitor when we receive the capture result corresponding to the request
+ // below.
+ val listener = Result3AStateListenerImpl(mapOf())
+ graphListener3A.addListener(listener)
+
+ debug { "unlock3AForCapture - sending a request to turn off ae." }
+ if (!graphProcessor.submit(mapOf<CaptureRequest.Key<*>, Any>(CONTROL_AE_LOCK to false))) {
+ debug { "unlock3AForCapture - request to unlock ae failed." }
+ graphListener3A.removeListener(listener)
+ return CompletableDeferred(result3ASubmitFailed)
+ }
+
+ return listener.getDeferredResult()
+ }
+
+ /**
+ * For API level 23 or newer versions, the sending a request with
+ * CONTROL_AE_PRECAPTURE_TRIGGER = CANCEL can be used to unlock the camera device's
+ * internally locked AE. REF :
+ * https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ */
+ @RequiresApi(23)
+ private suspend fun unlock3APostCaptureAndroidMAndAbove(): Deferred<Result3A> {
+ debug { "unlock3APostCapture - sending a request to reset af and ae precapture metering." }
+ val parametersForAePrecaptureAndAfCancel = mapOf<CaptureRequest.Key<*>, Any>(
+ CONTROL_AF_TRIGGER to CONTROL_AF_TRIGGER_CANCEL,
+ CONTROL_AE_PRECAPTURE_TRIGGER to
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL
+ )
+ if (!graphProcessor.submit(parametersForAePrecaptureAndAfCancel)) {
+ debug {
+ "unlock3APostCapture - request to reset af and ae precapture metering failed, " +
+ "returning early."
+ }
+ return CompletableDeferred(result3ASubmitFailed)
+ }
+
+ // Sending a request with ae precapture trigger = cancel does not have any specific affect
+ // on the ae state, so we don't need to listen for a specific state. As long as the request
+ // successfully reaches the camera device and the capture request corresponding to that
+ // request arrives back, it should suffice.
+ val listener = Result3AStateListenerImpl(
+ mapOf<CaptureResult.Key<*>, List<Any>>(
+ CaptureResult.CONTROL_AF_STATE to afUnlockedStateList
+ )
+ )
+ graphListener3A.addListener(listener)
+ graphProcessor.invalidate()
+ return listener.getDeferredResult()
+ }
+
private suspend fun lock3ANow(
aeLockBehavior: Lock3ABehavior?,
afLockBehavior: Lock3ABehavior?,
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3AForCaptureTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3AForCaptureTest.kt
new file mode 100644
index 0000000..c4b4465
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/Controller3AForCaptureTest.kt
@@ -0,0 +1,247 @@
+/*
+ * 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.camera.camera2.pipe.impl
+
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.os.Build
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestNumber
+import androidx.camera.camera2.pipe.Status3A
+import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.testing.CameraPipeRobolectricTestRunner
+import androidx.camera.camera2.pipe.testing.FakeFrameMetadata
+import androidx.camera.camera2.pipe.testing.FakeGraphProcessor
+import androidx.camera.camera2.pipe.testing.FakeRequestMetadata
+import androidx.camera.camera2.pipe.testing.FakeRequestProcessor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(CameraPipeRobolectricTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class Controller3AForCaptureTest {
+ private val graphProcessor = FakeGraphProcessor()
+ private val graphState3A = GraphState3A()
+ private val requestProcessor = FakeRequestProcessor(graphState3A)
+ private val listener3A = Listener3A()
+ private val controller3A = Controller3A(graphProcessor, graphState3A, listener3A)
+
+ @Test
+ fun testLock3AForCapture(): Unit = runBlocking {
+ initGraphProcessor()
+
+ val result = controller3A.lock3AForCapture()
+ assertThat(result.isCompleted).isFalse()
+
+ // Since requirement is to trigger both AF and AE precapture metering. The result of
+ // lock3AForCapture call will complete once AE and AF have reached their desired states. In
+ // this response i.e cameraResponse1, AF is still scanning so the result won't be complete.
+ val cameraResponse = GlobalScope.async {
+ listener3A.onRequestSequenceCreated(
+ FakeRequestMetadata(
+ requestNumber = RequestNumber(1)
+ )
+ )
+ listener3A.onPartialCaptureResult(
+ FakeRequestMetadata(requestNumber = RequestNumber(1)),
+ FrameNumber(101L),
+ FakeFrameMetadata(
+ frameNumber = FrameNumber(101L),
+ resultMetadata = mapOf(
+ CaptureResult.CONTROL_AF_STATE to CaptureResult
+ .CONTROL_AF_STATE_PASSIVE_SCAN,
+ CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_SEARCHING
+ )
+ )
+ )
+ }
+
+ cameraResponse.await()
+ assertThat(result.isCompleted).isFalse()
+
+ // One we are notified that the AE and AF are in the desired states, the result of
+ // lock3AForCapture call will complete.
+ GlobalScope.launch {
+ listener3A.onRequestSequenceCreated(
+ FakeRequestMetadata(
+ requestNumber = RequestNumber(1)
+ )
+ )
+ listener3A.onPartialCaptureResult(
+ FakeRequestMetadata(requestNumber = RequestNumber(1)),
+ FrameNumber(101L),
+ FakeFrameMetadata(
+ frameNumber = FrameNumber(101L),
+ resultMetadata = mapOf(
+ CaptureResult.CONTROL_AF_STATE to CaptureResult
+ .CONTROL_AF_STATE_FOCUSED_LOCKED,
+ CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED
+ )
+ )
+ )
+ }
+
+ val result3A = result.await()
+ assertThat(result3A.frameNumber.value).isEqualTo(101L)
+ assertThat(result3A.status).isEqualTo(Status3A.OK)
+
+ // We now check if the correct sequence of requests were submitted by lock3AForCapture call.
+ // There should be a request to trigger AF and AE precapture metering.
+ val request1 = requestProcessor.nextEvent().request
+ assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+ CaptureRequest.CONTROL_AF_TRIGGER_START
+ )
+ assertThat(request1.extraRequestParameters[CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER])
+ .isEqualTo(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
+ }
+
+ @Test
+ fun testUnlock3APostCapture() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ testUnlock3APostCaptureAndroidMAndAbove()
+ } else {
+ testUnlock3APostCaptureAndroidLAndBelow()
+ }
+ }
+
+ private fun testUnlock3APostCaptureAndroidMAndAbove(): Unit = runBlocking {
+ initGraphProcessor()
+
+ val result = controller3A.unlock3APostCapture()
+ assertThat(result.isCompleted).isFalse()
+
+ // In this response i.e cameraResponse1, AF is still scanning so the result won't be
+ // complete.
+ val cameraResponse = GlobalScope.async {
+ listener3A.onRequestSequenceCreated(
+ FakeRequestMetadata(
+ requestNumber = RequestNumber(1)
+ )
+ )
+ listener3A.onPartialCaptureResult(
+ FakeRequestMetadata(requestNumber = RequestNumber(1)),
+ FrameNumber(101L),
+ FakeFrameMetadata(
+ frameNumber = FrameNumber(101L),
+ resultMetadata = mapOf(
+ CaptureResult.CONTROL_AF_STATE to CaptureResult
+ .CONTROL_AF_STATE_FOCUSED_LOCKED,
+ CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED
+ )
+ )
+ )
+ }
+
+ cameraResponse.await()
+ assertThat(result.isCompleted).isFalse()
+
+ // Once we are notified that the AF is in unlocked state, the result of unlock3APostCapture
+ // call will complete. For AE we don't need to to check for a specific state, receiving the
+ // capture result corresponding to the submitted request suffices.
+ GlobalScope.launch {
+ listener3A.onRequestSequenceCreated(
+ FakeRequestMetadata(
+ requestNumber = RequestNumber(1)
+ )
+ )
+ listener3A.onPartialCaptureResult(
+ FakeRequestMetadata(requestNumber = RequestNumber(1)),
+ FrameNumber(101L),
+ FakeFrameMetadata(
+ frameNumber = FrameNumber(101L),
+ resultMetadata = mapOf(
+ CaptureResult.CONTROL_AF_STATE to CaptureResult
+ .CONTROL_AF_STATE_PASSIVE_SCAN,
+ CaptureResult.CONTROL_AE_STATE to CaptureResult.CONTROL_AE_STATE_CONVERGED
+ )
+ )
+ )
+ }
+
+ val result3A = result.await()
+ assertThat(result3A.frameNumber.value).isEqualTo(101L)
+ assertThat(result3A.status).isEqualTo(Status3A.OK)
+
+ // We now check if the correct sequence of requests were submitted by unlock3APostCapture
+ // call. There should be a request to cancel AF and AE precapture metering.
+ val request1 = requestProcessor.nextEvent().request
+ assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+ CaptureRequest.CONTROL_AF_TRIGGER_CANCEL
+ )
+ assertThat(request1.extraRequestParameters[CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER])
+ .isEqualTo(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL)
+ }
+
+ private fun testUnlock3APostCaptureAndroidLAndBelow(): Unit = runBlocking {
+ initGraphProcessor()
+
+ val result = controller3A.unlock3APostCapture()
+ assertThat(result.isCompleted).isFalse()
+
+ val cameraResponse = GlobalScope.async {
+ listener3A.onRequestSequenceCreated(
+ FakeRequestMetadata(
+ requestNumber = RequestNumber(1)
+ )
+ )
+ listener3A.onPartialCaptureResult(
+ FakeRequestMetadata(requestNumber = RequestNumber(1)),
+ FrameNumber(101L),
+ FakeFrameMetadata(
+ frameNumber = FrameNumber(101L),
+ resultMetadata = mapOf()
+ )
+ )
+ }
+
+ cameraResponse.await()
+ val result3A = result.await()
+ assertThat(result3A.frameNumber.value).isEqualTo(101L)
+ assertThat(result3A.status).isEqualTo(Status3A.OK)
+
+ // We now check if the correct sequence of requests were submitted by unlock3APostCapture
+ // call. There should be a request to cancel AF and lock ae.
+ val request1 = requestProcessor.nextEvent().request
+ assertThat(request1!!.extraRequestParameters[CaptureRequest.CONTROL_AF_TRIGGER]).isEqualTo(
+ CaptureRequest.CONTROL_AF_TRIGGER_CANCEL
+ )
+ assertThat(request1.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK])
+ .isEqualTo(true)
+
+ // Then another request to unlock ae.
+ val request2 = requestProcessor.nextEvent().request
+ assertThat(request2!!.extraRequestParameters[CaptureRequest.CONTROL_AE_LOCK])
+ .isEqualTo(false)
+ }
+
+ private fun initGraphProcessor() {
+ graphProcessor.attach(requestProcessor)
+ graphProcessor.setRepeating(Request(streams = listOf(StreamId(1))))
+ }
+
+ companion object {
+ // The time duration in milliseconds between two frame results.
+ private const val FRAME_RATE_MS = 33L
+ }
+}
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index 21a45dc..fa36b55 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -5,6 +5,21 @@
method @VisibleForTesting public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function1<? super kotlinx.coroutines.CoroutineScope,? extends androidx.compose.animation.core.AnimationClockObservable> p);
}
+ public final class AnimateAsStateKt {
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.DpOffset> animateAsState-4E4yWWY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ }
+
public abstract class AnimatedFloat extends androidx.compose.animation.core.BaseAnimatedValue<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
ctor public AnimatedFloat(androidx.compose.animation.core.AnimationClockObservable clock, float visibilityThreshold);
method public final float getMax();
diff --git a/compose/animation/animation-core/api/public_plus_experimental_current.txt b/compose/animation/animation-core/api/public_plus_experimental_current.txt
index 21a45dc..fa36b55 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_current.txt
@@ -5,6 +5,21 @@
method @VisibleForTesting public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function1<? super kotlinx.coroutines.CoroutineScope,? extends androidx.compose.animation.core.AnimationClockObservable> p);
}
+ public final class AnimateAsStateKt {
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.DpOffset> animateAsState-4E4yWWY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ }
+
public abstract class AnimatedFloat extends androidx.compose.animation.core.BaseAnimatedValue<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
ctor public AnimatedFloat(androidx.compose.animation.core.AnimationClockObservable clock, float visibilityThreshold);
method public final float getMax();
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 4640820..85ea8c2 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -5,6 +5,21 @@
method @VisibleForTesting public static void setRootAnimationClockFactory(kotlin.jvm.functions.Function1<? super kotlinx.coroutines.CoroutineScope,? extends androidx.compose.animation.core.AnimationClockObservable> p);
}
+ public final class AnimateAsStateKt {
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Float> animateAsState(float targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Bounds> animateAsState(androidx.compose.ui.unit.Bounds targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> animateAsState(androidx.compose.ui.geometry.Rect targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.lang.Integer> animateAsState(int targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T targetValue, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> androidx.compose.runtime.State<T> animateAsState(T? targetValue, androidx.compose.animation.core.TwoWayConverter<T,V> typeConverter, optional androidx.compose.animation.core.AnimationSpec<T> animationSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> animateAsState-2AXSKHY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.DpOffset> animateAsState-4E4yWWY(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> animateAsState-Cmzki-s(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> animateAsState-Lz7ev7o(float targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> animateAsState-rlPqr8Y(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? finishedListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> animateAsState-t81mtYE(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? finishedListener);
+ }
+
public abstract class AnimatedFloat extends androidx.compose.animation.core.BaseAnimatedValue<java.lang.Float,androidx.compose.animation.core.AnimationVector1D> {
ctor public AnimatedFloat(androidx.compose.animation.core.AnimationClockObservable clock, float visibilityThreshold);
method public final float getMax();
diff --git a/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatedValueSamples.kt b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatedValueSamples.kt
new file mode 100644
index 0000000..d2a677c
--- /dev/null
+++ b/compose/animation/animation-core/samples/src/main/java/androidx/compose/animation/core/samples/AnimatedValueSamples.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.animation.core.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.animation.core.AnimationVector2D
+import androidx.compose.animation.core.TwoWayConverter
+import androidx.compose.animation.core.animateAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.preferredSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+
+@Sampled
+@Composable
+fun AlphaAnimationSample() {
+ @Composable
+ fun alphaAnimation(visible: Boolean) {
+ // Animates to 1f or 0f based on [visible].
+ // This [animateState] returns a State<Float> object. The value of the State object is
+ // being updated by animation. (This method is overloaded for different parameter types.)
+ // Here we use the returned [State] object as a property delegate.
+ val alpha: Float by animateAsState(if (visible) 1f else 0f)
+ Box(modifier = Modifier.background(Color.Red).alpha(alpha))
+ }
+}
+
+data class MySize(val width: Dp, val height: Dp)
+
+@Sampled
+@Composable
+fun ArbitraryValueTypeTransitionSample() {
+ @Composable
+ fun ArbitraryValueTypeAnimation(enabled: Boolean) {
+ // Sets up the different animation target values based on the [enabled] flag.
+ val mySize = remember(enabled) {
+ if (enabled) {
+ MySize(500.dp, 500.dp)
+ } else {
+ MySize(100.dp, 100.dp)
+ }
+ }
+
+ // Animates a custom type value to the given target value, using a [TwoWayConverter]. The
+ // converter tells the animation system how to convert the custom type from and to
+ // [AnimationVector], so that it can be animated.
+ val animSize: MySize by animateAsState<MySize, AnimationVector2D>(
+ mySize,
+ TwoWayConverter(
+ convertToVector = { AnimationVector2D(it.width.value, it.height.value) },
+ convertFromVector = { MySize(it.v1.dp, it.v2.dp) }
+ )
+ )
+ Box(Modifier.preferredSize(animSize.width, animSize.height).background(color = Color.Red))
+ }
+}
+
+@Sampled
+@Composable
+fun DpAnimationSample() {
+ @Composable
+ fun HeightAnimation(collapsed: Boolean) {
+ // Animates a height of [Dp] type to different target values based on the [collapsed] flag.
+ val height: Dp by animateAsState(if (collapsed) 10.dp else 20.dp)
+ Box(Modifier.fillMaxWidth().height(height).background(color = Color.Red))
+ }
+}
+
+@Sampled
+@Composable
+@Suppress("UNUSED_VARIABLE")
+fun AnimateOffsetSample() {
+ @Composable
+ fun OffsetAnimation(selected: Boolean) {
+ // Animates the offset depending on the selected flag.
+ // [animateAsState] returns a State<Offset> object. The value of the State object is
+ // updated by the animation. Here we use that State<Offset> as a property delegate.
+ val offset: Offset by animateAsState(
+ if (selected) Offset(0f, 0f) else Offset(20f, 20f)
+ )
+
+ // In this example, animateAsState returns a State<IntOffset>. The value of the returned
+ // State object is updated by the animation.
+ val intOffset: IntOffset by animateAsState(
+ if (selected) IntOffset(0, 0) else IntOffset(50, 50)
+ )
+ }
+}
diff --git a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
new file mode 100644
index 0000000..e3cd31a
--- /dev/null
+++ b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
@@ -0,0 +1,447 @@
+/*
+ * 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.animation.core
+
+import androidx.compose.animation.animateAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.dispatch.withFrameNanos
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.test.ExperimentalTesting
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Bounds
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+@OptIn(ExperimentalTesting::class)
+class SingleValueAnimationTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun animate1DTest() {
+ fun <T> myTween(): TweenSpec<T> =
+ TweenSpec(
+ easing = FastOutSlowInEasing,
+ durationMillis = 100
+ )
+
+ var enabled by mutableStateOf(false)
+ rule.setContent {
+ Box {
+ val animationValue by animateAsState(
+ if (enabled) 50.dp else 250.dp, myTween()
+ )
+ // TODO: Properly test this with a deterministic clock when the test framework is
+ // ready
+ if (enabled) {
+ LaunchedEffect(Unit) {
+ assertEquals(250.dp, animationValue)
+ val startTime = withFrameNanos { it }
+ var frameTime = startTime
+ do {
+ val playTime = (frameTime - startTime) / 1_000_000L
+ val fraction = FastOutSlowInEasing.invoke(playTime / 100f)
+ val expected = lerp(250f, 50f, fraction)
+ assertEquals(expected.dp, animationValue)
+ frameTime = withFrameNanos { it }
+ } while (frameTime - startTime <= 100_000_000L)
+ // Animation is finished at this point
+ assertEquals(50.dp, animationValue)
+ }
+ }
+ }
+ }
+ rule.runOnIdle { enabled = true }
+ rule.waitForIdle()
+ }
+
+ @Test
+ fun animate1DOnCoroutineTest() {
+ var enabled by mutableStateOf(false)
+ rule.setContent {
+ Box {
+ var animationValue by remember { mutableStateOf(250f) }
+ // Animate from 250f to 50f when enable flips to true
+ animationValue = animateAsState(
+ if (enabled) 50f else 250f, tween(200, easing = FastOutLinearInEasing)
+ ).value
+ // TODO: Properly test this with a deterministic clock when the test framework is
+ // ready
+ if (enabled) {
+ LaunchedEffect(Unit) {
+ assertEquals(250f, animationValue)
+ val startTime = withFrameNanos { it }
+ var frameTime = startTime
+ do {
+ val playTime = (frameTime - startTime) / 1_000_000L
+ val fraction = FastOutLinearInEasing.invoke(playTime / 200f)
+ val expected = lerp(250f, 50f, fraction)
+ assertEquals(expected, animationValue)
+ frameTime = withFrameNanos { it }
+ } while (frameTime - startTime <= 200_000_000L)
+ // Animation is finished at this point
+ assertEquals(50f, animationValue)
+ }
+ }
+ }
+ }
+ rule.runOnIdle { enabled = true }
+ rule.waitForIdle()
+ }
+
+ @Test
+ fun animate2DTest() {
+
+ val startVal = AnimationVector(120f, 56f)
+ val endVal = AnimationVector(0f, 77f)
+
+ fun <V> tween(): TweenSpec<V> =
+ TweenSpec(
+ easing = LinearEasing,
+ durationMillis = 100
+ )
+
+ var enabled by mutableStateOf(false)
+ rule.setContent {
+ Box {
+ val vectorValue by animateAsState(
+ if (enabled) endVal else startVal,
+ tween()
+ )
+
+ val positionValue by animateAsState(
+ if (enabled)
+ DpOffset.VectorConverter.convertFromVector(endVal)
+ else
+ DpOffset.VectorConverter.convertFromVector(startVal),
+ tween()
+ )
+
+ val sizeValue by animateAsState(
+ if (enabled)
+ Size.VectorConverter.convertFromVector(endVal)
+ else
+ Size.VectorConverter.convertFromVector(startVal),
+ tween()
+ )
+
+ val pxPositionValue by animateAsState(
+ if (enabled)
+ Offset.VectorConverter.convertFromVector(endVal)
+ else
+ Offset.VectorConverter.convertFromVector(startVal),
+ tween()
+ )
+
+ if (enabled) {
+ LaunchedEffect(Unit) {
+ val startTime = withFrameNanos { it }
+ var frameTime = startTime
+ do {
+ val playTime = (frameTime - startTime) / 1_000_000L
+ val expect = AnimationVector(
+ lerp(startVal.v1, endVal.v1, playTime / 100f),
+ lerp(startVal.v2, endVal.v2, playTime / 100f)
+ )
+
+ assertEquals(expect, vectorValue)
+ assertEquals(Size.VectorConverter.convertFromVector(expect), sizeValue)
+ assertEquals(
+ DpOffset.VectorConverter.convertFromVector(expect),
+ positionValue
+ )
+ assertEquals(
+ Offset.VectorConverter.convertFromVector(expect),
+ pxPositionValue
+ )
+ frameTime = withFrameNanos { it }
+ } while (frameTime - startTime <= 100_000_000L)
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle { enabled = true }
+ rule.waitForIdle()
+ }
+
+ @Test
+ fun animate4DRectTest() {
+ val startVal = AnimationVector(30f, -76f, 280f, 35f)
+ val endVal = AnimationVector(-42f, 89f, 77f, 100f)
+
+ fun <V> tween(): TweenSpec<V> =
+ TweenSpec(
+ easing = LinearOutSlowInEasing,
+ durationMillis = 100
+ )
+
+ var enabled by mutableStateOf(false)
+ rule.setContent {
+ Box {
+ val vectorValue by animateAsState(
+ if (enabled) endVal else startVal,
+ tween()
+ )
+
+ val boundsValue by animateAsState(
+ if (enabled)
+ Bounds.VectorConverter.convertFromVector(endVal)
+ else
+ Bounds.VectorConverter.convertFromVector(startVal),
+ tween()
+ )
+
+ val pxBoundsValue by animateAsState(
+ if (enabled)
+ Rect.VectorConverter.convertFromVector(endVal)
+ else
+ Rect.VectorConverter.convertFromVector(startVal),
+ tween()
+ )
+
+ if (enabled) {
+ LaunchedEffect(Unit) {
+ val startTime = withFrameNanos { it }
+ var frameTime = startTime
+ do {
+ val playTime = (frameTime - startTime) / 1_000_000L
+
+ val fraction = LinearOutSlowInEasing.invoke(playTime / 100f)
+ val expect = AnimationVector(
+ lerp(startVal.v1, endVal.v1, fraction),
+ lerp(startVal.v2, endVal.v2, fraction),
+ lerp(startVal.v3, endVal.v3, fraction),
+ lerp(startVal.v4, endVal.v4, fraction)
+ )
+
+ assertEquals(expect, vectorValue)
+ assertEquals(
+ Bounds.VectorConverter.convertFromVector(expect),
+ boundsValue
+ )
+ assertEquals(
+ Rect.VectorConverter.convertFromVector(expect),
+ pxBoundsValue
+ )
+ frameTime = withFrameNanos { it }
+ } while (frameTime - startTime <= 100_000_000L)
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle { enabled = true }
+ rule.waitForIdle()
+ }
+
+ @Test
+ fun animate4DTest() {
+ val startVal = AnimationVector(30f, -76f, 280f, 35f)
+ val endVal = AnimationVector(-42f, 89f, 77f, 100f)
+
+ fun <V> tween(): TweenSpec<V> =
+ TweenSpec(
+ easing = LinearOutSlowInEasing,
+ durationMillis = 100
+ )
+
+ var enabled by mutableStateOf(false)
+ rule.setContent {
+ Box {
+ val vectorValue by animateAsState(
+ if (enabled) endVal else startVal,
+ tween()
+ )
+
+ val boundsValue by animateAsState(
+ if (enabled)
+ Bounds.VectorConverter.convertFromVector(endVal)
+ else
+ Bounds.VectorConverter.convertFromVector(startVal),
+ tween()
+ )
+ if (enabled) {
+ LaunchedEffect(Unit) {
+ val startTime = withFrameNanos { it }
+ var frameTime = startTime
+ do {
+ val playTime = (frameTime - startTime) / 1_000_000L
+ val fraction = LinearOutSlowInEasing.invoke(playTime / 100f)
+ val expect = AnimationVector(
+ lerp(startVal.v1, endVal.v1, fraction),
+ lerp(startVal.v2, endVal.v2, fraction),
+ lerp(startVal.v3, endVal.v3, fraction),
+ lerp(startVal.v4, endVal.v4, fraction)
+ )
+
+ assertEquals(expect, vectorValue)
+ assertEquals(
+ Bounds.VectorConverter.convertFromVector(expect),
+ boundsValue
+ )
+ frameTime = withFrameNanos { it }
+ } while (frameTime - startTime <= 100_000_000L)
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle { enabled = true }
+ rule.waitForIdle()
+ }
+
+ @Test
+ fun animateColorTest() {
+ var enabled by mutableStateOf(false)
+ rule.setContent {
+ Box {
+ val value by animateAsState(
+ if (enabled) Color.Cyan else Color.Black,
+ TweenSpec(
+ durationMillis = 100,
+ easing = FastOutLinearInEasing
+ )
+ )
+ if (enabled) {
+ LaunchedEffect(Unit) {
+ val startTime = withFrameNanos { it }
+ var frameTime = startTime
+ do {
+ val playTime = (frameTime - startTime) / 1_000_000L
+ val fraction = FastOutLinearInEasing.invoke(playTime / 100f)
+ val expected = lerp(Color.Black, Color.Cyan, fraction)
+ assertEquals(expected, value)
+ frameTime = withFrameNanos { it }
+ } while (frameTime - startTime <= 100_000_000L)
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle { enabled = true }
+ rule.waitForIdle()
+ }
+
+ @Test
+ fun visibilityThresholdTest() {
+
+ val specForFloat = FloatSpringSpec(visibilityThreshold = 0.01f)
+ val specForVector = FloatSpringSpec(visibilityThreshold = 0.5f)
+ val specForOffset = FloatSpringSpec(visibilityThreshold = 0.5f)
+ val specForBounds = FloatSpringSpec(visibilityThreshold = 0.1f)
+
+ var enabled by mutableStateOf(false)
+ rule.setContent {
+ Box {
+ val vectorValue by animateAsState(
+ if (enabled) AnimationVector(100f) else AnimationVector(0f),
+ visibilityThreshold = AnimationVector(0.5f)
+ )
+
+ val offsetValue by animateAsState(
+ if (enabled)
+ Offset(100f, 100f)
+ else
+ Offset(0f, 0f)
+ )
+
+ val boundsValue by animateAsState(
+ if (enabled)
+ Bounds(100.dp, 100.dp, 100.dp, 100.dp)
+ else
+ Bounds(0.dp, 0.dp, 0.dp, 0.dp)
+ )
+
+ val floatValue by animateAsState(if (enabled) 100f else 0f)
+
+ val durationForFloat = specForFloat.getDurationMillis(0f, 100f, 0f)
+ val durationForVector = specForVector.getDurationMillis(0f, 100f, 0f)
+ val durationForOffset = specForOffset.getDurationMillis(0f, 100f, 0f)
+ val durationForBounds = specForBounds.getDurationMillis(0f, 100f, 0f)
+
+ if (enabled) {
+ LaunchedEffect(Unit) {
+ val startTime = withFrameNanos { it }
+ var frameTime = startTime
+ do {
+ val playTime = (frameTime - startTime) / 1_000_000L
+ val expectFloat = specForFloat.getValue(playTime, 0f, 100f, 0f)
+ assertEquals("play time: $playTime", expectFloat, floatValue)
+
+ if (playTime < durationForVector) {
+ val expectVector = specForVector.getValue(playTime, 0f, 100f, 0f)
+ assertEquals(AnimationVector(expectVector), vectorValue)
+ } else {
+ assertEquals(AnimationVector(100f), vectorValue)
+ }
+
+ if (playTime < durationForOffset) {
+ val expectOffset = specForOffset.getValue(playTime, 0f, 100f, 0f)
+ assertEquals(Offset(expectOffset, expectOffset), offsetValue)
+ } else {
+ assertEquals(Offset(100f, 100f), offsetValue)
+ }
+
+ if (playTime < durationForBounds) {
+ val expectBounds = specForBounds.getValue(playTime, 0f, 100f, 0f)
+ assertEquals(
+ Bounds(
+ expectBounds.dp,
+ expectBounds.dp,
+ expectBounds.dp,
+ expectBounds.dp
+ ),
+ boundsValue
+ )
+ } else {
+ assertEquals(
+ Bounds(100.dp, 100.dp, 100.dp, 100.dp),
+ boundsValue
+ )
+ }
+
+ frameTime = withFrameNanos { it }
+ } while (frameTime - startTime <= durationForFloat)
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle { enabled = true }
+ rule.waitForIdle()
+ }
+}
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
new file mode 100644
index 0000000..ebd6cdd
--- /dev/null
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
@@ -0,0 +1,502 @@
+/*
+ * 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.animation.core
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.unit.Bounds
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+
+private val defaultAnimation = spring<Float>()
+
+/**
+ * Fire-and-forget animation function for [Float]. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedFloat][androidx.compose.animation.animatedFloat] for cancelable
+ * animations.
+ *
+ * @sample androidx.compose.animation.core.samples.AlphaAnimationSample
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. [spring]
+ * will be used by default.
+ * @param visibilityThreshold An optional threshold for deciding when the animation value is
+ * considered close enough to the targetValue.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun animateAsState(
+ targetValue: Float,
+ animationSpec: AnimationSpec<Float> = defaultAnimation,
+ visibilityThreshold: Float = 0.01f,
+ finishedListener: ((Float) -> Unit)? = null
+): State<Float> {
+ val resolvedAnimSpec =
+ if (animationSpec === defaultAnimation) {
+ remember(visibilityThreshold) { spring(visibilityThreshold = visibilityThreshold) }
+ } else {
+ animationSpec
+ }
+ val animationState: AnimationState<Float, AnimationVector1D> = remember {
+ AnimationState(targetValue)
+ }
+
+ val currentEndListener by rememberUpdatedState(finishedListener)
+ LaunchedEffect(targetValue, animationSpec) {
+ animationState.animateTo(
+ targetValue,
+ resolvedAnimSpec,
+ // If the previous animation was interrupted (i.e. not finished), make it sequential.
+ !animationState.isFinished
+ )
+ currentEndListener?.invoke(animationState.value)
+ }
+ return animationState
+}
+
+/**
+ * Fire-and-forget animation function for [Dp]. This Composable function is overloaded for
+ * different parameter types such as [Float], [Color][androidx.compose.ui.graphics.Color], [Offset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * @sample androidx.compose.animation.core.samples.DpAnimationSample
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun animateAsState(
+ targetValue: Dp,
+ animationSpec: AnimationSpec<Dp> = remember {
+ spring(visibilityThreshold = Dp.VisibilityThreshold)
+ },
+ finishedListener: ((Dp) -> Unit)? = null
+): State<Dp> {
+ return animateAsState(
+ targetValue,
+ Dp.VectorConverter,
+ animationSpec,
+ finishedListener = finishedListener
+ )
+}
+
+/**
+ * Fire-and-forget animation function for [DpOffset]. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * val position: DpOffset by animateAsState(
+ * if (selected) DpOffset(0.dp, 0.dp) else DpOffset(20.dp, 20.dp))
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun animateAsState(
+ targetValue: DpOffset,
+ animationSpec: AnimationSpec<DpOffset> = remember {
+ spring(visibilityThreshold = DpOffset.VisibilityThreshold)
+ },
+ finishedListener: ((DpOffset) -> Unit)? = null
+): State<DpOffset> {
+ return animateAsState(
+ targetValue, DpOffset.VectorConverter, animationSpec, finishedListener = finishedListener
+ )
+}
+
+/**
+ * Fire-and-forget animation function for [Size]. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * val size: Size by animateAsState(
+ * if (selected) Size(20f, 20f) else Size(10f, 10f))
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun animateAsState(
+ targetValue: Size,
+ animationSpec: AnimationSpec<Size> = remember {
+ spring(visibilityThreshold = Size.VisibilityThreshold)
+ },
+ finishedListener: ((Size) -> Unit)? = null
+): State<Size> {
+ return animateAsState(
+ targetValue,
+ Size.VectorConverter,
+ animationSpec,
+ finishedListener = finishedListener
+ )
+}
+
+/**
+ * Fire-and-forget animation function for [Bounds]. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * val bounds: Bounds by animateAsState(
+ * if (collapsed) Bounds(0.dp, 0.dp, 10.dp, 20.dp) else Bounds(0.dp, 0.dp, 100.dp, 200.dp))
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun animateAsState(
+ targetValue: Bounds,
+ animationSpec: AnimationSpec<Bounds> = remember {
+ spring(visibilityThreshold = Bounds.VisibilityThreshold)
+ },
+ finishedListener: ((Bounds) -> Unit)? = null
+): State<Bounds> {
+ return animateAsState(
+ targetValue,
+ Bounds.VectorConverter,
+ animationSpec,
+ finishedListener = finishedListener
+ )
+}
+
+/**
+ * Fire-and-forget animation function for [Offset]. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Float],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * @sample androidx.compose.animation.core.samples.AnimateOffsetSample
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun animateAsState(
+ targetValue: Offset,
+ animationSpec: AnimationSpec<Offset> = remember {
+ spring(visibilityThreshold = Offset.VisibilityThreshold)
+ },
+ finishedListener: ((Offset) -> Unit)? = null
+): State<Offset> {
+ return animateAsState(
+ targetValue, Offset.VectorConverter, animationSpec, finishedListener = finishedListener
+ )
+}
+
+/**
+ * Fire-and-forget animation function for [Rect]. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * val bounds: Rect by animateAsState(
+ * if (enabled) Rect(0f, 0f, 100f, 100f) else Rect(8f, 8f, 80f, 80f))
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun animateAsState(
+ targetValue: Rect,
+ animationSpec: AnimationSpec<Rect> = remember {
+ spring(visibilityThreshold = Rect.VisibilityThreshold)
+ },
+ finishedListener: ((Rect) -> Unit)? = null
+): State<Rect> {
+ return animateAsState(
+ targetValue, Rect.VectorConverter, animationSpec, finishedListener = finishedListener
+ )
+}
+
+/**
+ * Fire-and-forget animation function for [Int]. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun animateAsState(
+ targetValue: Int,
+ animationSpec: AnimationSpec<Int> = remember { spring(visibilityThreshold = 1) },
+ finishedListener: ((Int) -> Unit)? = null
+): State<Int> {
+ return animateAsState(
+ targetValue, Int.VectorConverter, animationSpec, finishedListener = finishedListener
+ )
+}
+
+/**
+ * Fire-and-forget animation function for [IntOffset]. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * @sample androidx.compose.animation.core.samples.AnimateOffsetSample
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun animateAsState(
+ targetValue: IntOffset,
+ animationSpec: AnimationSpec<IntOffset> = remember {
+ spring(visibilityThreshold = IntOffset.VisibilityThreshold)
+ },
+ finishedListener: ((IntOffset) -> Unit)? = null
+): State<IntOffset> {
+ return animateAsState(
+ targetValue, IntOffset.VectorConverter, animationSpec, finishedListener = finishedListener
+ )
+}
+
+/**
+ * Fire-and-forget animation function for [IntSize]. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun animateAsState(
+ targetValue: IntSize,
+ animationSpec: AnimationSpec<IntSize> = remember {
+ spring(visibilityThreshold = IntSize.VisibilityThreshold)
+ },
+ finishedListener: ((IntSize) -> Unit)? = null
+): State<IntSize> {
+ return animateAsState(
+ targetValue, IntSize.VectorConverter, animationSpec, finishedListener = finishedListener
+ )
+}
+
+/**
+ * Fire-and-forget animation function for [AnimationVector]. This Composable function is overloaded
+ * for different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset]
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param visibilityThreshold An optional threshold to define when the animation value can be
+ * considered close enough to the targetValue to end the animation.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun <T : AnimationVector> animateAsState(
+ targetValue: T,
+ animationSpec: AnimationSpec<T> = remember {
+ spring(visibilityThreshold = visibilityThreshold)
+ },
+ visibilityThreshold: T? = null,
+ finishedListener: ((T) -> Unit)? = null
+): State<T> {
+ return animateAsState(
+ targetValue,
+ remember { TwoWayConverter<T, T>({ it }, { it }) },
+ animationSpec,
+ finishedListener = finishedListener
+ )
+}
+
+/**
+ * Fire-and-forget animation function for any value. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Color][androidx.compose.ui.graphics.Color], [Offset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedValue][androidx.compose.animation.animatedValue] for cancelable
+ * animations.
+ *
+ * @sample androidx.compose.animation.core.samples.ArbitraryValueTypeTransitionSample
+ *
+ * data class MySize(val width: Dp, val height: Dp)
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param visibilityThreshold An optional threshold to define when the animation value can be
+ * considered close enough to the targetValue to end the animation.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ * @return A [State] object, the value of which is updated by animation.
+ */
+@Composable
+fun <T, V : AnimationVector> animateAsState(
+ targetValue: T,
+ typeConverter: TwoWayConverter<T, V>,
+ animationSpec: AnimationSpec<T> = remember {
+ spring(visibilityThreshold = visibilityThreshold)
+ },
+ visibilityThreshold: T? = null,
+ finishedListener: ((T) -> Unit)? = null
+): State<T> {
+ val animationState: AnimationState<T, V> = remember(typeConverter) {
+ AnimationState(targetValue, typeConverter = typeConverter)
+ }
+
+ val listener by rememberUpdatedState(finishedListener)
+ LaunchedEffect(targetValue, animationSpec) {
+ animationState.animateTo(
+ targetValue,
+ animationSpec,
+ // If the previous animation was interrupted (i.e. not finished), make it sequential.
+ !animationState.isFinished
+ )
+ listener?.invoke(animationState.value)
+ }
+ return animationState
+}
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationVectors.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationVectors.kt
index 4dd5e69..566a0c4 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationVectors.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimationVectors.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:Suppress("RedundantOverride")
-
package androidx.compose.animation.core
/**
@@ -122,7 +120,7 @@
override fun equals(other: Any?): Boolean =
other is AnimationVector1D && other.value == value
- override fun hashCode(): Int = super.hashCode()
+ override fun hashCode(): Int = value.hashCode()
}
/**
@@ -174,7 +172,7 @@
override fun equals(other: Any?): Boolean =
other is AnimationVector2D && other.v1 == v1 && other.v2 == v2
- override fun hashCode(): Int = super.hashCode()
+ override fun hashCode(): Int = v1.hashCode() * 31 + v2.hashCode()
}
/**
@@ -237,7 +235,7 @@
override fun equals(other: Any?): Boolean =
other is AnimationVector3D && other.v1 == v1 && other.v2 == v2 && other.v3 == v3
- override fun hashCode(): Int = super.hashCode()
+ override fun hashCode(): Int = (v1.hashCode() * 31 + v2.hashCode()) * 31 + v3.hashCode()
}
/**
@@ -312,5 +310,6 @@
other.v3 == v3 &&
other.v4 == v4
- override fun hashCode(): Int = super.hashCode()
+ override fun hashCode(): Int =
+ ((v1.hashCode() * 31 + v2.hashCode()) * 31 + v3.hashCode()) * 31 + v4.hashCode()
}
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationClockTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationClockTest.kt
index 9dab8b4..4a359af 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationClockTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationClockTest.kt
@@ -17,16 +17,12 @@
package androidx.compose.animation.core
import junit.framework.TestCase.assertEquals
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import kotlin.coroutines.Continuation
-import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@RunWith(JUnit4::class)
@@ -38,66 +34,6 @@
clock = ManualAnimationClock(0L)
}
- /**
- * This function tests BaseAnimationClock's ability to handle an addition to its subscribers
- * while it is processing other subscribers.
- *
- * First a blocking subscription is added - when the time changes it will hang the execution
- * of all the subscriptions in the clock
- *
- * Second, we change the time to trigger said subscription
- *
- * While the clock is being blocked, we subscribe a second observer - at this time it should
- * not be triggered
- *
- * Lastly, we unblock the execution and the second observer should have an execution on the
- * same frame
- */
- @Test
- fun testSubscriptionDuringFrameCallback() {
- lateinit var blockingObserverContinuation: Continuation<Unit>
- val blockingSubscription = ignoreFirstFrameObserver { _ ->
- blockWithContinuation { blockingObserverContinuation = it }
- }
-
- // Subscribe a blocking subscription - it will hang clock.dispatchTime until
- // blockingObserverContinuation is resumed.
- clock.subscribe(blockingSubscription)
-
- // Block the thread until we dispatch the frame callbacks in another thread
- lateinit var frameCallbackJob: Job
- blockWithContinuation {
- val assertionContinuation = it
- // Call dispatchTime and allow it to hang
- frameCallbackJob = GlobalScope.launch {
- assertionContinuation.resume(Unit)
- clock.clockTimeMillis += 100
- }
- }
-
- var newObserverFrameTime = -1L
-
- val newObserver = ignoreFirstFrameObserver {
- newObserverFrameTime = it
- }
-
- // Subscribe a observer while dispatchTime is hanging
- clock.subscribe(newObserver)
-
- assertEquals(-1L, newObserverFrameTime)
-
- // Unblock the dispatchTime
- blockingObserverContinuation.resume(Unit)
-
- // Allow dispatchTime to finish its work
- runBlocking {
- frameCallbackJob.join()
- }
-
- // Within the same frame, the new subscription should have called back.
- assertEquals(100L, newObserverFrameTime)
- }
-
@Test
fun testRemovalBeforeAdd() {
val recordedFrameTimes = mutableListOf<Long>()
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationVectorTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationVectorTest.kt
index f616e31..b71db2e 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationVectorTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimationVectorTest.kt
@@ -39,6 +39,45 @@
}
@Test
+ fun testHashCode() {
+ // Expect equal
+ assertEquals(
+ AnimationVector1D(0f).hashCode(),
+ AnimationVector(1f).apply { reset() }.hashCode()
+ )
+ assertEquals(
+ AnimationVector2D(0f, 0f).hashCode(),
+ AnimationVector(1f, 2f).apply { reset() }.hashCode()
+ )
+ assertEquals(
+ AnimationVector3D(0f, 0f, 0f).hashCode(),
+ AnimationVector(1f, 2f, 3f).apply { reset() }.hashCode()
+ )
+ assertEquals(
+ AnimationVector4D(0f, 0f, 0f, 0f).hashCode(),
+ AnimationVector(1f, 2f, 3f, 4f).apply { reset() }.hashCode()
+ )
+
+ // Expect *not* equal
+ assertNotEquals(
+ AnimationVector1D(0f).hashCode(),
+ AnimationVector(1f).hashCode()
+ )
+ assertNotEquals(
+ AnimationVector2D(2f, 1f).hashCode(),
+ AnimationVector(1f, 2f).hashCode()
+ )
+ assertNotEquals(
+ AnimationVector3D(1f, 2f, 3f).hashCode(),
+ AnimationVector(1f, 2f, 3f).apply { reset() }.hashCode()
+ )
+ assertNotEquals(
+ AnimationVector4D(4f, 3f, 2f, 1f).hashCode(),
+ AnimationVector(1f, 2f, 3f, 4f).hashCode()
+ )
+ }
+
+ @Test
fun testAnimationVectorFactoryMethod() {
assertEquals(AnimationVector1D(200f), AnimationVector(200f))
assertEquals(AnimationVector2D(7f, 500f), AnimationVector(7f, 500f))
diff --git a/compose/animation/animation/api/current.txt b/compose/animation/animation/api/current.txt
index e6e29ea..49f9db8 100644
--- a/compose/animation/animation/api/current.txt
+++ b/compose/animation/animation/api/current.txt
@@ -134,19 +134,20 @@
}
public final class SingleValueAnimationKt {
- method @androidx.compose.runtime.Composable public static float animate(float target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static int animate(int target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> T animate(T target, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> T! animate(T? target, androidx.compose.animation.core.TwoWayConverter<T,V> converter, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-2AXSKHY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-4E4yWWY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-Cmzki-s(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static float animate-Lz7ev7o(float target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-m3E411Q(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-rlPqr8Y(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-t81mtYE(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static float animate(float target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static int animate(int target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> T animate(T target, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> T! animate(T? target, androidx.compose.animation.core.TwoWayConverter<T,V> converter, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-2AXSKHY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-4E4yWWY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-Cmzki-s(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static float animate-Lz7ev7o(float target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-m3E411Q(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-rlPqr8Y(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-t81mtYE(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? endListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
}
public final class TransitionKt {
diff --git a/compose/animation/animation/api/public_plus_experimental_current.txt b/compose/animation/animation/api/public_plus_experimental_current.txt
index e6e29ea..49f9db8 100644
--- a/compose/animation/animation/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation/api/public_plus_experimental_current.txt
@@ -134,19 +134,20 @@
}
public final class SingleValueAnimationKt {
- method @androidx.compose.runtime.Composable public static float animate(float target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static int animate(int target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> T animate(T target, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> T! animate(T? target, androidx.compose.animation.core.TwoWayConverter<T,V> converter, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-2AXSKHY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-4E4yWWY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-Cmzki-s(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static float animate-Lz7ev7o(float target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-m3E411Q(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-rlPqr8Y(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-t81mtYE(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static float animate(float target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static int animate(int target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> T animate(T target, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> T! animate(T? target, androidx.compose.animation.core.TwoWayConverter<T,V> converter, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-2AXSKHY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-4E4yWWY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-Cmzki-s(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static float animate-Lz7ev7o(float target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-m3E411Q(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-rlPqr8Y(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-t81mtYE(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? endListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
}
public final class TransitionKt {
diff --git a/compose/animation/animation/api/restricted_current.txt b/compose/animation/animation/api/restricted_current.txt
index e6e29ea..49f9db8 100644
--- a/compose/animation/animation/api/restricted_current.txt
+++ b/compose/animation/animation/api/restricted_current.txt
@@ -134,19 +134,20 @@
}
public final class SingleValueAnimationKt {
- method @androidx.compose.runtime.Composable public static float animate(float target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static int animate(int target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> T animate(T target, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> T! animate(T? target, androidx.compose.animation.core.TwoWayConverter<T,V> converter, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-2AXSKHY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-4E4yWWY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-Cmzki-s(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static float animate-Lz7ev7o(float target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-m3E411Q(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-rlPqr8Y(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? endListener);
- method @androidx.compose.runtime.Composable public static long animate-t81mtYE(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static float animate(float target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animSpec, optional float visibilityThreshold, optional kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.unit.Bounds animate(androidx.compose.ui.unit.Bounds target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Bounds> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Bounds,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.ui.geometry.Rect animate(androidx.compose.ui.geometry.Rect target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static int animate(int target, optional androidx.compose.animation.core.AnimationSpec<java.lang.Integer> animSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T extends androidx.compose.animation.core.AnimationVector> T animate(T target, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T, V extends androidx.compose.animation.core.AnimationVector> T! animate(T? target, androidx.compose.animation.core.TwoWayConverter<T,V> converter, optional androidx.compose.animation.core.AnimationSpec<T> animSpec, optional T? visibilityThreshold, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-2AXSKHY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-4E4yWWY(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.DpOffset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpOffset,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-Cmzki-s(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static float animate-Lz7ev7o(float target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-m3E411Q(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-rlPqr8Y(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,kotlin.Unit>? endListener);
+ method @Deprecated @androidx.compose.runtime.Composable public static long animate-t81mtYE(long target, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset> animSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit>? endListener);
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> animateAsState-m3E411Q(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,kotlin.Unit>? finishedListener);
}
public final class TransitionKt {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt
index 33ddc3a..7cb7a93 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SingleValueAnimationDemo.kt
@@ -16,12 +16,13 @@
package androidx.compose.animation.demos
-import androidx.compose.animation.animate
+import androidx.compose.animation.animateAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@@ -30,7 +31,7 @@
@Composable
fun SingleValueAnimationDemo() {
val enabled = remember { mutableStateOf(true) }
- val color = animate(if (enabled.value) Color.Green else Color.Red)
+ val color by animateAsState(if (enabled.value) Color.Green else Color.Red)
Box(
Modifier.fillMaxSize().clickable { enabled.value = !enabled.value }.background(color)
)
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt
index f4c2774..712e84a 100644
--- a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedValueSamples.kt
@@ -18,6 +18,7 @@
import androidx.annotation.Sampled
import androidx.compose.animation.animate
+import androidx.compose.animation.animateAsState
import androidx.compose.animation.core.AnimationVector2D
import androidx.compose.animation.core.TwoWayConverter
import androidx.compose.foundation.background
@@ -25,8 +26,10 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.preferredSize
+import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@@ -36,6 +39,7 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
+@Suppress("DEPRECATION")
@Sampled
@Composable
fun VisibilityTransitionSample() {
@@ -46,6 +50,7 @@
}
}
+@Suppress("DEPRECATION")
@Sampled
@Composable
fun ColorTransitionSample() {
@@ -58,6 +63,7 @@
data class MySize(val width: Dp, val height: Dp)
+@Suppress("DEPRECATION")
@Sampled
@Composable
fun ArbitraryValueTypeTransitionSample() {
@@ -81,6 +87,7 @@
}
}
+@Suppress("DEPRECATION")
@Sampled
@Composable
fun DpAnimationSample() {
@@ -93,7 +100,7 @@
@Sampled
@Composable
-@Suppress("UNUSED_VARIABLE")
+@Suppress("UNUSED_VARIABLE", "DEPRECATION")
fun AnimateOffsetSample() {
@Composable
fun OffsetTransition(selected: Boolean) {
@@ -106,3 +113,18 @@
)
}
}
+
+@Sampled
+@Composable
+fun ColorAnimationSample() {
+ @Composable
+ fun ColorAnimation(primary: Boolean) {
+ // Animates to primary or secondary color, depending on whether [primary] is true
+ // [animateState] returns the current animation value in a State<Color> in this example. We
+ // use the State<Color> object as a property delegate here.
+ val color: Color by animateAsState(
+ if (primary) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
+ )
+ Box(modifier = Modifier.background(color))
+ }
+}
\ No newline at end of file
diff --git a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/SingleValueAnimationTest.kt b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/SingleValueAnimationTest.kt
deleted file mode 100644
index 3b9d7c8..0000000
--- a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/SingleValueAnimationTest.kt
+++ /dev/null
@@ -1,420 +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.animation
-
-import androidx.compose.animation.core.AnimationVector
-import androidx.compose.animation.core.FastOutLinearInEasing
-import androidx.compose.animation.core.FastOutSlowInEasing
-import androidx.compose.animation.core.FloatSpringSpec
-import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.LinearOutSlowInEasing
-import androidx.compose.animation.core.VectorConverter
-import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.layout.Box
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.dispatch.withFrameNanos
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.lerp
-import androidx.compose.ui.test.ExperimentalTesting
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.unit.Bounds
-import androidx.compose.ui.unit.DpOffset
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.lerp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import org.junit.Assert.assertEquals
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@MediumTest
-@OptIn(ExperimentalTesting::class)
-class SingleValueAnimationTest {
-
- @get:Rule
- val rule = createComposeRule()
-
- @Test
- fun animate1DTest() {
- val startVal = 20f
- val endVal = 250f
-
- var dpValue = startVal.dp
-
- fun <T> tween(): TweenSpec<T> =
- TweenSpec(
- easing = FastOutSlowInEasing,
- durationMillis = 100
- )
-
- val content: @Composable (Boolean) -> Unit = { enabled ->
- dpValue = animate(
- if (enabled) endVal.dp else startVal.dp,
- tween()
- )
- }
-
- val verify: () -> Unit = {
- for (i in 0..100 step 50) {
- val value = lerp(
- startVal, endVal,
- FastOutSlowInEasing.invoke(i / 100f)
- )
- assertEquals(value.dp, dpValue)
- rule.clockTestRule.advanceClock(50)
- rule.waitForIdle()
- }
- }
-
- animateTest(content, verify)
- }
-
- @Test
- fun animate1DOnCoroutineTest() {
- var enabled by mutableStateOf(false)
- rule.setContent {
- Box {
- var animationValue by remember { mutableStateOf(250f) }
- // Animate from 250f to 50f when enable flips to true
- animationValue = animate(
- if (enabled) 50f else 250f, tween(200, easing = FastOutLinearInEasing)
- )
- // TODO: Properly test this with a deterministic clock when the test framework is
- // ready
- if (enabled) {
- LaunchedEffect(Unit) {
- assertEquals(250f, animationValue)
- val startTime = withFrameNanos { it }
- var frameTime = startTime
- do {
- val playTime = (frameTime - startTime) / 1_000_000L
- val fraction = FastOutLinearInEasing.invoke(playTime / 200f)
- val expected = lerp(250f, 50f, fraction)
- assertEquals(expected, animationValue)
- frameTime = withFrameNanos { it }
- } while (frameTime - startTime <= 200_000_000L)
- // Animation is finished at this point
- assertEquals(50f, animationValue)
- }
- }
- }
- }
- rule.runOnIdle { enabled = true }
- rule.waitForIdle()
- }
-
- @Test
- fun animate2DTest() {
-
- val startVal = AnimationVector(120f, 56f)
- val endVal = AnimationVector(0f, 77f)
-
- var vectorValue = startVal
- var positionValue = DpOffset.VectorConverter.convertFromVector(startVal)
- var sizeValue = Size.VectorConverter.convertFromVector(startVal)
- var pxPositionValue = Offset.VectorConverter.convertFromVector(startVal)
-
- fun <V> tween(): TweenSpec<V> =
- TweenSpec(
- easing = LinearEasing,
- durationMillis = 100
- )
-
- val content: @Composable (Boolean) -> Unit = { enabled ->
- vectorValue = animate(
- if (enabled) endVal else startVal,
- tween()
- )
-
- positionValue = animate(
- if (enabled)
- DpOffset.VectorConverter.convertFromVector(endVal)
- else
- DpOffset.VectorConverter.convertFromVector(startVal),
- tween()
- )
-
- sizeValue = animate(
- if (enabled)
- Size.VectorConverter.convertFromVector(endVal)
- else
- Size.VectorConverter.convertFromVector(startVal),
- tween()
- )
-
- pxPositionValue = animate(
- if (enabled)
- Offset.VectorConverter.convertFromVector(endVal)
- else
- Offset.VectorConverter.convertFromVector(startVal),
- tween()
- )
- }
-
- val verify: () -> Unit = {
- for (i in 0..100 step 50) {
- val expect = AnimationVector(
- lerp(startVal.v1, endVal.v1, i / 100f),
- lerp(startVal.v2, endVal.v2, i / 100f)
- )
-
- assertEquals(expect, vectorValue)
- assertEquals(Size.VectorConverter.convertFromVector(expect), sizeValue)
- assertEquals(DpOffset.VectorConverter.convertFromVector(expect), positionValue)
- assertEquals(Offset.VectorConverter.convertFromVector(expect), pxPositionValue)
- rule.clockTestRule.advanceClock(50)
- rule.waitForIdle()
- }
- }
-
- animateTest(content, verify)
- }
-
- @Test
- fun animate4DRectTest() {
- val startVal = AnimationVector(30f, -76f, 280f, 35f)
- val endVal = AnimationVector(-42f, 89f, 77f, 100f)
-
- var vectorValue = startVal
- var boundsValue = Bounds.VectorConverter.convertFromVector(startVal)
- var pxBoundsValue = Rect.VectorConverter.convertFromVector(startVal)
-
- fun <V> tween(): TweenSpec<V> =
- TweenSpec(
- easing = LinearOutSlowInEasing,
- durationMillis = 100
- )
-
- val content: @Composable (Boolean) -> Unit = { enabled ->
- vectorValue = animate(
- if (enabled) endVal else startVal,
- tween()
- )
-
- boundsValue = animate(
- if (enabled)
- Bounds.VectorConverter.convertFromVector(endVal)
- else
- Bounds.VectorConverter.convertFromVector(startVal),
- tween()
- )
-
- pxBoundsValue = animate(
- if (enabled)
- Rect.VectorConverter.convertFromVector(endVal)
- else
- Rect.VectorConverter.convertFromVector(startVal),
- tween()
- )
- }
-
- val verify: () -> Unit = {
- for (i in 0..100 step 50) {
- val fraction = LinearOutSlowInEasing.invoke(i / 100f)
- val expect = AnimationVector(
- lerp(startVal.v1, endVal.v1, fraction),
- lerp(startVal.v2, endVal.v2, fraction),
- lerp(startVal.v3, endVal.v3, fraction),
- lerp(startVal.v4, endVal.v4, fraction)
- )
-
- assertEquals(expect, vectorValue)
- assertEquals(Bounds.VectorConverter.convertFromVector(expect), boundsValue)
- assertEquals(Rect.VectorConverter.convertFromVector(expect), pxBoundsValue)
- rule.clockTestRule.advanceClock(50)
- rule.waitForIdle()
- }
- }
-
- animateTest(content, verify)
- }
-
- @Suppress("DEPRECATION")
- @Test
- fun animate4DTest() {
- val startVal = AnimationVector(30f, -76f, 280f, 35f)
- val endVal = AnimationVector(-42f, 89f, 77f, 100f)
-
- var vectorValue = startVal
- var boundsValue = Bounds.VectorConverter.convertFromVector(startVal)
-
- fun <V> tween(): TweenSpec<V> =
- TweenSpec(
- easing = LinearOutSlowInEasing,
- durationMillis = 100
- )
-
- val content: @Composable (Boolean) -> Unit = { enabled ->
- vectorValue = animate(
- if (enabled) endVal else startVal,
- tween()
- )
-
- boundsValue = animate(
- if (enabled)
- Bounds.VectorConverter.convertFromVector(endVal)
- else
- Bounds.VectorConverter.convertFromVector(startVal),
- tween()
- )
- }
-
- val verify: () -> Unit = {
- for (i in 0..100 step 50) {
- val fraction = LinearOutSlowInEasing.invoke(i / 100f)
- val expect = AnimationVector(
- lerp(startVal.v1, endVal.v1, fraction),
- lerp(startVal.v2, endVal.v2, fraction),
- lerp(startVal.v3, endVal.v3, fraction),
- lerp(startVal.v4, endVal.v4, fraction)
- )
-
- assertEquals(expect, vectorValue)
- assertEquals(Bounds.VectorConverter.convertFromVector(expect), boundsValue)
- rule.clockTestRule.advanceClock(50)
- rule.waitForIdle()
- }
- }
-
- animateTest(content, verify)
- }
-
- @Test
- fun animateColorTest() {
- var value = Color.Black
- val content: @Composable (Boolean) -> Unit = { enabled ->
- value = animate(
- if (enabled) Color.Cyan else Color.Black,
- TweenSpec(
- durationMillis = 100,
- easing = FastOutLinearInEasing
- )
- )
- }
-
- val verify: () -> Unit = {
-
- for (i in 0..100 step 50) {
- val fraction = FastOutLinearInEasing.invoke(i / 100f)
- val expected = lerp(Color.Black, Color.Cyan, fraction)
- assertEquals(expected, value)
- rule.clockTestRule.advanceClock(50)
- rule.waitForIdle()
- }
- }
-
- animateTest(content, verify)
- }
-
- @Test
- fun visibilityThresholdTest() {
-
- var vectorValue = AnimationVector(0f)
- var offsetValue = Offset(0f, 0f)
- var boundsValue = Bounds(0.dp, 0.dp, 0.dp, 0.dp)
-
- val specForFloat = FloatSpringSpec(visibilityThreshold = 0.01f)
- val specForVector = FloatSpringSpec(visibilityThreshold = 0.5f)
- val specForOffset = FloatSpringSpec(visibilityThreshold = 0.5f)
- val specForBounds = FloatSpringSpec(visibilityThreshold = 0.1f)
-
- val content: @Composable (Boolean) -> Unit = { enabled ->
- vectorValue = animate(
- if (enabled) AnimationVector(100f) else AnimationVector(0f),
- visibilityThreshold = AnimationVector(0.5f)
- )
-
- offsetValue = animate(
- if (enabled)
- Offset(100f, 100f)
- else
- Offset(0f, 0f)
- )
-
- boundsValue = animate(
- if (enabled)
- Bounds(100.dp, 100.dp, 100.dp, 100.dp)
- else
- Bounds(0.dp, 0.dp, 0.dp, 0.dp)
- )
- }
-
- val durationForFloat = specForFloat.getDurationMillis(0f, 100f, 0f)
- val durationForVector = specForVector.getDurationMillis(0f, 100f, 0f)
- val durationForOffset = specForOffset.getDurationMillis(0f, 100f, 0f)
- val durationForBounds = specForBounds.getDurationMillis(0f, 100f, 0f)
- val verify: () -> Unit = {
- for (i in 0..durationForFloat step 50) {
-
- if (i < durationForVector) {
- val expectVector = specForVector.getValue(i, 0f, 100f, 0f)
- assertEquals(AnimationVector(expectVector), vectorValue)
- } else {
- assertEquals(AnimationVector(100f), vectorValue)
- }
-
- if (i < durationForOffset) {
- val expectOffset = specForOffset.getValue(i, 0f, 100f, 0f)
- assertEquals(Offset(expectOffset, expectOffset), offsetValue)
- } else {
- assertEquals(Offset(100f, 100f), offsetValue)
- }
-
- if (i < durationForBounds) {
- val expectBounds = specForBounds.getValue(i, 0f, 100f, 0f)
- assertEquals(
- Bounds(expectBounds.dp, expectBounds.dp, expectBounds.dp, expectBounds.dp),
- boundsValue
- )
- } else {
- assertEquals(Bounds(100.dp, 100.dp, 100.dp, 100.dp), boundsValue)
- }
-
- rule.clockTestRule.advanceClock(50)
- rule.waitForIdle()
- }
- }
-
- animateTest(content, verify)
- }
-
- private fun animateTest(content: @Composable (Boolean) -> Unit, verify: () -> Unit) {
-
- rule.clockTestRule.pauseClock()
- var enabled by mutableStateOf(false)
- rule.setContent {
- Box {
- content(enabled)
- }
- }
- rule.runOnIdle { enabled = true }
- rule.waitForIdle()
-
- verify()
- }
-}
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt
index eb27870..04a4d98 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/SingleValueAnimation.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("DEPRECATION")
+
package androidx.compose.animation
import androidx.compose.animation.core.AnimationEndReason
@@ -25,16 +27,19 @@
import androidx.compose.animation.core.TwoWayConverter
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.VisibilityThreshold
+import androidx.compose.animation.core.animateAsState
import androidx.compose.animation.core.animateTo
import androidx.compose.animation.core.isFinished
+import androidx.compose.animation.core.spring
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.onCommit
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
@@ -42,11 +47,12 @@
import androidx.compose.ui.platform.AmbientAnimationClock
import androidx.compose.ui.unit.Bounds
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.Position
-private val defaultAnimation = SpringSpec<Float>()
+private val defaultAnimation = spring<Float>()
/**
* Fire-and-forget animation [Composable] for [Float]. Once such an animation is created, it will be
@@ -65,6 +71,13 @@
* considered close enough to the target.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, visibilityThreshold, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun animate(
target: Float,
@@ -110,10 +123,17 @@
* animation will be used by default.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.animateAsState"
+ )
+)
@Composable
fun animate(
target: Color,
- animSpec: AnimationSpec<Color> = remember { SpringSpec() },
+ animSpec: AnimationSpec<Color> = remember { spring() },
endListener: ((Color) -> Unit)? = null
): Color {
val converter = remember(target.colorSpace) { (Color.VectorConverter)(target.colorSpace) }
@@ -135,11 +155,18 @@
* animation will be used by default.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun animate(
target: Dp,
animSpec: AnimationSpec<Dp> = remember {
- SpringSpec(visibilityThreshold = Dp.VisibilityThreshold)
+ spring(visibilityThreshold = Dp.VisibilityThreshold)
},
endListener: ((Dp) -> Unit)? = null
): Dp {
@@ -147,33 +174,38 @@
}
/**
- * Fire-and-forget animation [Composable] for [DpOffset]. Once such an animation is created, it will
+ * Fire-and-forget animation [Composable] for [Position]. Once such an animation is created, it will
* be positionally memoized, like other @[Composable]s. To trigger the animation, or alter the
* course of the animation, simply supply a different [target] to the [Composable].
*
* Note, [animateTo] is for simple animations that cannot be canceled. For cancellable animations
* see [animatedValue].
*
- * val position : DpOffset = animate(
- * if (selected) DpOffset(0.dp, 0.dp) else DpOffset(20.dp, 20.dp))
+ * val position : Position = animate(
+ * if (selected) Position(0.dp, 0.dp) else Position(20.dp, 20.dp))
*
* @param target Target value of the animation
* @param animSpec The animation that will be used to change the value through time. Physics
* animation will be used by default.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun animate(
- target: DpOffset,
- animSpec: AnimationSpec<DpOffset> = remember {
- SpringSpec(
- visibilityThreshold = DpOffset.VisibilityThreshold
- )
+ target: Position,
+ animSpec: AnimationSpec<Position> = remember {
+ spring(visibilityThreshold = Position.VisibilityThreshold)
},
- endListener: ((DpOffset) -> Unit)? = null
-): DpOffset {
+ endListener: ((Position) -> Unit)? = null
+): Position {
return animate(
- target, DpOffset.VectorConverter, animSpec, endListener = endListener
+ target, Position.VectorConverter, animSpec, endListener = endListener
)
}
@@ -193,11 +225,18 @@
* animation will be used by default.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun animate(
target: Size,
animSpec: AnimationSpec<Size> = remember {
- SpringSpec(visibilityThreshold = Size.VisibilityThreshold)
+ spring(visibilityThreshold = Size.VisibilityThreshold)
},
endListener: ((Size) -> Unit)? = null
): Size {
@@ -220,11 +259,18 @@
* animation will be used by default.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun animate(
target: Bounds,
animSpec: AnimationSpec<Bounds> = remember {
- SpringSpec(visibilityThreshold = Bounds.VisibilityThreshold)
+ spring(visibilityThreshold = Bounds.VisibilityThreshold)
},
endListener: ((Bounds) -> Unit)? = null
): Bounds {
@@ -251,11 +297,18 @@
* animation will be used by default.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun animate(
target: Offset,
animSpec: AnimationSpec<Offset> = remember {
- SpringSpec(visibilityThreshold = Offset.VisibilityThreshold)
+ spring(visibilityThreshold = Offset.VisibilityThreshold)
},
endListener: ((Offset) -> Unit)? = null
): Offset {
@@ -280,11 +333,18 @@
* animation will be used by default.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun animate(
target: Rect,
animSpec: AnimationSpec<Rect> = remember {
- SpringSpec(visibilityThreshold = Rect.VisibilityThreshold)
+ spring(visibilityThreshold = Rect.VisibilityThreshold)
},
endListener: ((Rect) -> Unit)? = null
): Rect {
@@ -306,12 +366,17 @@
* animation will be used by default.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun animate(
target: Int,
- animSpec: AnimationSpec<Int> = remember {
- SpringSpec(visibilityThreshold = 1)
- },
+ animSpec: AnimationSpec<Int> = remember { spring(visibilityThreshold = 1) },
endListener: ((Int) -> Unit)? = null
): Int {
return animate(
@@ -334,11 +399,18 @@
* animation will be used by default.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun animate(
target: IntOffset,
animSpec: AnimationSpec<IntOffset> = remember {
- SpringSpec(visibilityThreshold = IntOffset.VisibilityThreshold)
+ spring(visibilityThreshold = IntOffset.VisibilityThreshold)
},
endListener: ((IntOffset) -> Unit)? = null
): IntOffset {
@@ -360,11 +432,18 @@
* animation will be used by default.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun animate(
target: IntSize,
animSpec: AnimationSpec<IntSize> = remember {
- SpringSpec(visibilityThreshold = IntSize.VisibilityThreshold)
+ spring(visibilityThreshold = IntSize.VisibilityThreshold)
},
endListener: ((IntSize) -> Unit)? = null
): IntSize {
@@ -388,11 +467,18 @@
* considered close enough to the target to end the animation.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, animSpec, visibilityThreshold, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun <T : AnimationVector> animate(
target: T,
animSpec: AnimationSpec<T> = remember {
- SpringSpec(visibilityThreshold = visibilityThreshold)
+ spring(visibilityThreshold = visibilityThreshold)
},
visibilityThreshold: T? = null,
endListener: ((T) -> Unit)? = null
@@ -424,12 +510,19 @@
* considered close enough to the target to end the animation.
* @param endListener An optional end listener to get notified when the animation is finished.
*/
+@Deprecated(
+ "animate has been replaced with animateAsState",
+ ReplaceWith(
+ "animateAsState(target, converter, animSpec, visibilityThreshold, endListener).value",
+ "androidx.compose.animation.core.animateAsState"
+ )
+)
@Composable
fun <T, V : AnimationVector> animate(
target: T,
converter: TwoWayConverter<T, V>,
animSpec: AnimationSpec<T> = remember {
- SpringSpec(visibilityThreshold = visibilityThreshold)
+ spring(visibilityThreshold = visibilityThreshold)
},
visibilityThreshold: T? = null,
endListener: ((T) -> Unit)? = null
@@ -451,4 +544,39 @@
}
}
return anim.value
+}
+
+/**
+ * Fire-and-forget animation function for [Color]. This Composable function is overloaded for
+ * different parameter types such as [Dp], [Float], [Int], [Size], [Offset], [DpOffset],
+ * etc. When the provided [targetValue] is changed, the animation will run automatically. If there
+ * is already an animation in-flight whe [targetValue] changes, the on-going animation will adjust
+ * course to animate towards the new target value.
+ *
+ * [animateAsState] returns a [State] object. The value of the state object will continuously be
+ * updated by the animation until the animation finishes.
+ *
+ * Note, [animateAsState] cannot be canceled/stopped without removing this composable function
+ * from the tree. See [animatedColor][androidx.compose.animation.animatedColor] for cancelable
+ * animations.
+ *
+ * @sample androidx.compose.animation.samples.ColorAnimationSample
+ *
+ * @param targetValue Target value of the animation
+ * @param animationSpec The animation that will be used to change the value through time. Physics
+ * animation will be used by default.
+ * @param finishedListener An optional end listener to get notified when the animation is finished.
+ */
+@Composable
+fun animateAsState(
+ targetValue: Color,
+ animationSpec: AnimationSpec<Color> = remember { spring() },
+ finishedListener: ((Color) -> Unit)? = null
+): State<Color> {
+ val converter = remember(targetValue.colorSpace) {
+ (Color.VectorConverter)(targetValue.colorSpace)
+ }
+ return animateAsState(
+ targetValue, converter, animationSpec, finishedListener = finishedListener
+ )
}
\ No newline at end of file
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
index fb54ac1..7a9a0de 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
@@ -15,7 +15,7 @@
*/
package androidx.compose.desktop.examples.example1
-import androidx.compose.animation.animate
+import androidx.compose.animation.animateAsState
import androidx.compose.animation.core.TweenSpec
import androidx.compose.desktop.AppWindow
import androidx.compose.desktop.DesktopMaterialTheme
@@ -382,9 +382,9 @@
}
val enabled = remember { mutableStateOf(true) }
- val color = animate(
+ val color by animateAsState(
if (enabled.value) Color.Green else Color.Red,
- animSpec = TweenSpec(durationMillis = 2000)
+ animationSpec = TweenSpec(durationMillis = 2000)
)
MaterialTheme {
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 6cdc91d..0cd8a20 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -35,7 +35,7 @@
}
public final class ClickableKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
public final class ClickableTextKt {
@@ -125,7 +125,7 @@
}
public final class ProgressSemanticsKt {
- method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier progressSemantics(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=1.0) float progress);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier progressSemantics(androidx.compose.ui.Modifier, float value, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0) int steps);
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier progressSemantics(androidx.compose.ui.Modifier);
}
@@ -158,26 +158,6 @@
method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.foundation.ScrollState,?> Saver(androidx.compose.foundation.animation.FlingConfig flingConfig, androidx.compose.animation.core.AnimationClockObservable animationClock, androidx.compose.foundation.InteractionState? interactionState);
}
- public final class Strings {
- method public String getChecked();
- method public String getInProgress();
- method public String getIndeterminate();
- method public String getNotSelected();
- method public String getSelected();
- method public String getTemplatePercent();
- method public String getToggle();
- method public String getUnchecked();
- property public final String Checked;
- property public final String InProgress;
- property public final String Indeterminate;
- property public final String NotSelected;
- property public final String Selected;
- property public final String TemplatePercent;
- property public final String Toggle;
- property public final String Unchecked;
- field public static final androidx.compose.foundation.Strings INSTANCE;
- }
-
public final class TextKt {
method @Deprecated @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method @Deprecated @androidx.compose.runtime.Composable public static void Text-TPAwlIA(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional androidx.compose.ui.text.style.TextOverflow overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
@@ -431,12 +411,12 @@
package androidx.compose.foundation.selection {
public final class SelectableKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier selectable(androidx.compose.ui.Modifier, boolean selected, optional boolean enabled, optional boolean inMutuallyExclusiveGroup, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier selectable(androidx.compose.ui.Modifier, boolean selected, optional boolean enabled, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
public final class ToggleableKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier toggleable(androidx.compose.ui.Modifier, boolean value, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onValueChange);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier triStateToggleable(androidx.compose.ui.Modifier, androidx.compose.ui.state.ToggleableState state, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier toggleable(androidx.compose.ui.Modifier, boolean value, optional boolean enabled, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onValueChange);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier triStateToggleable(androidx.compose.ui.Modifier, androidx.compose.ui.state.ToggleableState state, optional boolean enabled, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
}
@@ -517,8 +497,8 @@
}
public final class BasicTextFieldKt {
- method @androidx.compose.runtime.Composable public static void BasicTextField-3Gdz-6o(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 androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
- method @androidx.compose.runtime.Composable public static void BasicTextField-UWLcGC4(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
+ method @androidx.compose.runtime.Composable public static void BasicTextField-55_Anxs(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 androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
+ method @androidx.compose.runtime.Composable public static void BasicTextField-HcQ_yMQ(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 androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
}
public final class BasicTextKt {
@@ -527,13 +507,16 @@
}
public final class CoreTextFieldKt {
- method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-Q2a5TJk(androidx.compose.ui.text.input.TextFieldValue value, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState? interactionState, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
+ method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-j_8B-p4(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 androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState? interactionState, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
}
public final class CoreTextKt {
method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, androidx.compose.ui.text.TextStyle style, boolean softWrap, androidx.compose.ui.text.style.TextOverflow overflow, int maxLines, java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout);
}
+ public final class InactiveTextFieldKt {
+ }
+
@androidx.compose.runtime.Immutable public final class InlineTextContent {
ctor public InlineTextContent(androidx.compose.ui.text.Placeholder placeholder, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> children);
method public androidx.compose.ui.text.Placeholder component1();
@@ -574,7 +557,6 @@
}
public final class MaxLinesHeightModifierKt {
- method public static androidx.compose.ui.Modifier maxLinesHeight(androidx.compose.ui.Modifier, @IntRange(from=1) int maxLines, androidx.compose.ui.text.TextStyle textStyle);
}
public final class TextFieldCursorKt {
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 6cdc91d..0cd8a20 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -35,7 +35,7 @@
}
public final class ClickableKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
public final class ClickableTextKt {
@@ -125,7 +125,7 @@
}
public final class ProgressSemanticsKt {
- method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier progressSemantics(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=1.0) float progress);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier progressSemantics(androidx.compose.ui.Modifier, float value, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0) int steps);
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier progressSemantics(androidx.compose.ui.Modifier);
}
@@ -158,26 +158,6 @@
method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.foundation.ScrollState,?> Saver(androidx.compose.foundation.animation.FlingConfig flingConfig, androidx.compose.animation.core.AnimationClockObservable animationClock, androidx.compose.foundation.InteractionState? interactionState);
}
- public final class Strings {
- method public String getChecked();
- method public String getInProgress();
- method public String getIndeterminate();
- method public String getNotSelected();
- method public String getSelected();
- method public String getTemplatePercent();
- method public String getToggle();
- method public String getUnchecked();
- property public final String Checked;
- property public final String InProgress;
- property public final String Indeterminate;
- property public final String NotSelected;
- property public final String Selected;
- property public final String TemplatePercent;
- property public final String Toggle;
- property public final String Unchecked;
- field public static final androidx.compose.foundation.Strings INSTANCE;
- }
-
public final class TextKt {
method @Deprecated @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method @Deprecated @androidx.compose.runtime.Composable public static void Text-TPAwlIA(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional androidx.compose.ui.text.style.TextOverflow overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
@@ -431,12 +411,12 @@
package androidx.compose.foundation.selection {
public final class SelectableKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier selectable(androidx.compose.ui.Modifier, boolean selected, optional boolean enabled, optional boolean inMutuallyExclusiveGroup, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier selectable(androidx.compose.ui.Modifier, boolean selected, optional boolean enabled, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
public final class ToggleableKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier toggleable(androidx.compose.ui.Modifier, boolean value, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onValueChange);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier triStateToggleable(androidx.compose.ui.Modifier, androidx.compose.ui.state.ToggleableState state, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier toggleable(androidx.compose.ui.Modifier, boolean value, optional boolean enabled, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onValueChange);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier triStateToggleable(androidx.compose.ui.Modifier, androidx.compose.ui.state.ToggleableState state, optional boolean enabled, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
}
@@ -517,8 +497,8 @@
}
public final class BasicTextFieldKt {
- method @androidx.compose.runtime.Composable public static void BasicTextField-3Gdz-6o(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 androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
- method @androidx.compose.runtime.Composable public static void BasicTextField-UWLcGC4(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
+ method @androidx.compose.runtime.Composable public static void BasicTextField-55_Anxs(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 androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
+ method @androidx.compose.runtime.Composable public static void BasicTextField-HcQ_yMQ(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 androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
}
public final class BasicTextKt {
@@ -527,13 +507,16 @@
}
public final class CoreTextFieldKt {
- method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-Q2a5TJk(androidx.compose.ui.text.input.TextFieldValue value, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState? interactionState, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
+ method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-j_8B-p4(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 androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState? interactionState, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
}
public final class CoreTextKt {
method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, androidx.compose.ui.text.TextStyle style, boolean softWrap, androidx.compose.ui.text.style.TextOverflow overflow, int maxLines, java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout);
}
+ public final class InactiveTextFieldKt {
+ }
+
@androidx.compose.runtime.Immutable public final class InlineTextContent {
ctor public InlineTextContent(androidx.compose.ui.text.Placeholder placeholder, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> children);
method public androidx.compose.ui.text.Placeholder component1();
@@ -574,7 +557,6 @@
}
public final class MaxLinesHeightModifierKt {
- method public static androidx.compose.ui.Modifier maxLinesHeight(androidx.compose.ui.Modifier, @IntRange(from=1) int maxLines, androidx.compose.ui.text.TextStyle textStyle);
}
public final class TextFieldCursorKt {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 6cdc91d..0cd8a20 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -35,7 +35,7 @@
}
public final class ClickableKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier clickable(androidx.compose.ui.Modifier, optional boolean enabled, optional String? onClickLabel, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, optional String? onLongClickLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
public final class ClickableTextKt {
@@ -125,7 +125,7 @@
}
public final class ProgressSemanticsKt {
- method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier progressSemantics(androidx.compose.ui.Modifier, @FloatRange(from=0.0, to=1.0) float progress);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier progressSemantics(androidx.compose.ui.Modifier, float value, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional @IntRange(from=0) int steps);
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier progressSemantics(androidx.compose.ui.Modifier);
}
@@ -158,26 +158,6 @@
method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.foundation.ScrollState,?> Saver(androidx.compose.foundation.animation.FlingConfig flingConfig, androidx.compose.animation.core.AnimationClockObservable animationClock, androidx.compose.foundation.InteractionState? interactionState);
}
- public final class Strings {
- method public String getChecked();
- method public String getInProgress();
- method public String getIndeterminate();
- method public String getNotSelected();
- method public String getSelected();
- method public String getTemplatePercent();
- method public String getToggle();
- method public String getUnchecked();
- property public final String Checked;
- property public final String InProgress;
- property public final String Indeterminate;
- property public final String NotSelected;
- property public final String Selected;
- property public final String TemplatePercent;
- property public final String Toggle;
- property public final String Unchecked;
- field public static final androidx.compose.foundation.Strings INSTANCE;
- }
-
public final class TextKt {
method @Deprecated @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method @Deprecated @androidx.compose.runtime.Composable public static void Text-TPAwlIA(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional androidx.compose.ui.text.style.TextOverflow overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
@@ -431,12 +411,12 @@
package androidx.compose.foundation.selection {
public final class SelectableKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier selectable(androidx.compose.ui.Modifier, boolean selected, optional boolean enabled, optional boolean inMutuallyExclusiveGroup, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier selectable(androidx.compose.ui.Modifier, boolean selected, optional boolean enabled, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
public final class ToggleableKt {
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier toggleable(androidx.compose.ui.Modifier, boolean value, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onValueChange);
- method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier triStateToggleable(androidx.compose.ui.Modifier, androidx.compose.ui.state.ToggleableState state, optional boolean enabled, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier toggleable(androidx.compose.ui.Modifier, boolean value, optional boolean enabled, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onValueChange);
+ method @androidx.compose.runtime.Composable public static androidx.compose.ui.Modifier triStateToggleable(androidx.compose.ui.Modifier, androidx.compose.ui.state.ToggleableState state, optional boolean enabled, optional androidx.compose.ui.semantics.Role? role, optional androidx.compose.foundation.InteractionState interactionState, optional androidx.compose.foundation.Indication? indication, kotlin.jvm.functions.Function0<kotlin.Unit> onClick);
}
}
@@ -517,8 +497,8 @@
}
public final class BasicTextFieldKt {
- method @androidx.compose.runtime.Composable public static void BasicTextField-3Gdz-6o(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 androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
- method @androidx.compose.runtime.Composable public static void BasicTextField-UWLcGC4(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
+ method @androidx.compose.runtime.Composable public static void BasicTextField-55_Anxs(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 androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
+ method @androidx.compose.runtime.Composable public static void BasicTextField-HcQ_yMQ(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 androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long cursorColor);
}
public final class BasicTextKt {
@@ -527,13 +507,16 @@
}
public final class CoreTextFieldKt {
- method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-Q2a5TJk(androidx.compose.ui.text.input.TextFieldValue value, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState? interactionState, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
+ method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreTextField-j_8B-p4(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 androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.ImeAction,kotlin.Unit> onImeActionPerformed, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState? interactionState, optional long cursorColor, optional boolean softWrap, optional androidx.compose.ui.text.input.ImeOptions imeOptions);
}
public final class CoreTextKt {
method @androidx.compose.runtime.Composable @androidx.compose.ui.text.InternalTextApi public static void CoreText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, androidx.compose.ui.text.TextStyle style, boolean softWrap, androidx.compose.ui.text.style.TextOverflow overflow, int maxLines, java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout);
}
+ public final class InactiveTextFieldKt {
+ }
+
@androidx.compose.runtime.Immutable public final class InlineTextContent {
ctor public InlineTextContent(androidx.compose.ui.text.Placeholder placeholder, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> children);
method public androidx.compose.ui.text.Placeholder component1();
@@ -574,7 +557,6 @@
}
public final class MaxLinesHeightModifierKt {
- method public static androidx.compose.ui.Modifier maxLinesHeight(androidx.compose.ui.Modifier, @IntRange(from=1) int maxLines, androidx.compose.ui.text.TextStyle textStyle);
}
public final class TextFieldCursorKt {
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 9994772..a3479cb 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -155,7 +155,6 @@
}
}
}
-
androidx {
name = "Compose Foundation"
publish = Publish.SNAPSHOT_AND_RELEASE
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt
index 354009e..1df150d 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt
@@ -26,6 +26,7 @@
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertHasClickAction
@@ -84,6 +85,7 @@
}
rule.onNodeWithTag("myClickable")
+ .assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Role))
.assertIsEnabled()
.assertHasClickAction()
}
@@ -100,6 +102,7 @@
}
rule.onNodeWithTag("myClickable")
+ .assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Role))
.assertIsNotEnabled()
.assertHasClickAction()
}
@@ -582,6 +585,7 @@
assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
"enabled",
"onClickLabel",
+ "role",
"onClick",
"onDoubleClick",
"onLongClick",
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt
index c938e06..5c24fc4 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt
@@ -26,6 +26,9 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.InspectableValue
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsSelected
@@ -76,6 +79,7 @@
rule.onAllNodes(isSelectable())
.assertCountEquals(1)
.onFirst()
+ .assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Role))
.assertIsSelected()
}
@@ -207,7 +211,7 @@
assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
"selected",
"enabled",
- "inMutuallyExclusiveGroup",
+ "role",
"interactionState",
"indication",
"onClick"
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt
index 89fcd8e..a5af97b 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt
@@ -29,6 +29,7 @@
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
@@ -107,15 +108,22 @@
SemanticsProperties.ToggleableState, ToggleableState.Indeterminate
)
+ fun roleNotSet(): SemanticsMatcher = SemanticsMatcher.keyNotDefined(
+ SemanticsProperties.Role
+ )
+
rule.onNodeWithTag("checkedToggleable")
+ .assert(roleNotSet())
.assertIsEnabled()
.assertIsOn()
.assertHasClickAction()
rule.onNodeWithTag("unCheckedToggleable")
+ .assert(roleNotSet())
.assertIsEnabled()
.assertIsOff()
.assertHasClickAction()
rule.onNodeWithTag("indeterminateToggleable")
+ .assert(roleNotSet())
.assertIsEnabled()
.assert(hasIndeterminateState())
.assertHasClickAction()
@@ -288,6 +296,7 @@
assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
"value",
"enabled",
+ "role",
"indication",
"interactionState",
"onValueChange",
@@ -305,6 +314,7 @@
assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
"state",
"enabled",
+ "role",
"indication",
"interactionState",
"onClick",
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/InactiveTextFieldTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/InactiveTextFieldTest.kt
new file mode 100644
index 0000000..ee5694b
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/InactiveTextFieldTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.foundation.textfield
+
+import androidx.compose.foundation.Interaction
+import androidx.compose.foundation.InteractionState
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.InactiveTextField
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.focus.FocusReference
+import androidx.compose.ui.focus.focusReference
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.selection.AmbientSelectionRegistrar
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class InactiveTextFieldTest {
+ @get:Rule
+ val rule = createComposeRule()
+
+ private val text = TextFieldValue("test")
+ private val tag = "InactiveTextField"
+
+ @Test
+ fun inactiveTextField_disabled_noFocus() {
+ val interactionState = InteractionState()
+ val focusReference = FocusReference()
+ rule.setContent {
+ InactiveTextField(
+ value = text,
+ modifier = Modifier.testTag(tag).focusReference(focusReference),
+ enabled = false,
+ interactionState = interactionState
+ )
+ }
+
+ rule.runOnIdle {
+ focusReference.requestFocus()
+ assertThat(interactionState.contains(Interaction.Focused)).isFalse()
+ }
+
+ rule.onNodeWithTag(tag)
+ .assertIsNotEnabled()
+ }
+
+ @Test
+ fun inactiveTextField_enabled_focusable() {
+ val interactionState = InteractionState()
+ val focusReference = FocusReference()
+ rule.setContent {
+ InactiveTextField(
+ value = text,
+ modifier = Modifier.testTag(tag).focusReference(focusReference),
+ enabled = true,
+ interactionState = interactionState
+ )
+ }
+ rule.runOnIdle {
+ assertThat(interactionState.contains(Interaction.Focused)).isFalse()
+ }
+
+ rule.runOnIdle {
+ focusReference.requestFocus()
+ assertThat(interactionState.contains(Interaction.Focused)).isTrue()
+ }
+ rule.onNodeWithTag(tag)
+ .assertIsFocused()
+ .assertIsEnabled()
+ }
+
+ @Test
+ fun inactiveTextField_disabled_noSelection() {
+ rule.setContent {
+ InactiveTextField(
+ value = text,
+ modifier = Modifier.testTag(tag).width(100.dp).composed {
+ assertThat(AmbientSelectionRegistrar.current).isNull()
+ Modifier
+ },
+ enabled = false
+ )
+ }
+ }
+
+ @Test
+ fun inactiveTextField_enabled_selectable() {
+ rule.setContent {
+ InactiveTextField(
+ value = text,
+ modifier = Modifier.composed {
+ assertThat(AmbientSelectionRegistrar.current).isNotNull()
+ Modifier
+ },
+ enabled = true
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MaxLinesHeightModifierTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/MaxLinesHeightModifierTest.kt
similarity index 98%
rename from compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MaxLinesHeightModifierTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/MaxLinesHeightModifierTest.kt
index 79c8407..b5c4f7f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MaxLinesHeightModifierTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/MaxLinesHeightModifierTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.compose.foundation
+package androidx.compose.foundation.textfield
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.width
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SoftwareKeyboardTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/SoftwareKeyboardTest.kt
similarity index 96%
rename from compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SoftwareKeyboardTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/SoftwareKeyboardTest.kt
index a3b940b..5dd938f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SoftwareKeyboardTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/SoftwareKeyboardTest.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.compose.foundation
+package androidx.compose.foundation.textfield
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.runtime.Providers
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldCursorTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldCursorTest.kt
similarity index 98%
rename from compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldCursorTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldCursorTest.kt
index fc5d1c5..f5b69ad 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldCursorTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldCursorTest.kt
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package androidx.compose.foundation
+package androidx.compose.foundation.textfield
import android.os.Build
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.preferredSize
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldDefaultWidthTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldDefaultWidthTest.kt
similarity index 98%
rename from compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldDefaultWidthTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldDefaultWidthTest.kt
index 8e79fae..f7c347d 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldDefaultWidthTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldDefaultWidthTest.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.compose.foundation
+package androidx.compose.foundation.textfield
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.defaultMinSizeConstraints
import androidx.compose.foundation.layout.preferredWidth
import androidx.compose.foundation.layout.width
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldFocusTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
similarity index 98%
rename from compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldFocusTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
index cdafb15..97c10d6 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldFocusTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.compose.foundation
+package androidx.compose.foundation.textfield
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.BasicTextField
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldOnValueChangeTextFieldValueTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldOnValueChangeTextFieldValueTest.kt
similarity index 98%
rename from compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldOnValueChangeTextFieldValueTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldOnValueChangeTextFieldValueTest.kt
index 9e08ff7..71e4faf 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldOnValueChangeTextFieldValueTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldOnValueChangeTextFieldValueTest.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.compose.foundation
+package androidx.compose.foundation.textfield
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.runtime.Providers
import androidx.compose.runtime.mutableStateOf
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldScrollTest.kt
similarity index 97%
rename from compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldScrollTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldScrollTest.kt
index 0009365..009a24c 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldScrollTest.kt
@@ -14,12 +14,17 @@
* limitations under the License.
*/
-package androidx.compose.foundation
+package androidx.compose.foundation.textfield
import android.os.Build
import androidx.compose.animation.core.ExponentialDecay
import androidx.compose.animation.core.ManualAnimationClock
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.InteractionState
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.ScrollableColumn
import androidx.compose.foundation.animation.FlingConfig
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.layout.preferredWidth
@@ -500,7 +505,8 @@
value,
VisualTransformation.None,
remember { InteractionState() },
- Ref()
+ Ref(),
+ true
) as InspectableValue
assertThat(modifier.nameFallback).isEqualTo("textFieldScroll")
assertThat(modifier.valueOverride).isNull()
@@ -510,7 +516,8 @@
"textFieldValue",
"visualTransformation",
"interactionState",
- "textLayoutResult"
+ "textLayoutResult",
+ "enabled"
)
}
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
similarity index 99%
rename from compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
index b7e6d80..647533c 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
@@ -1,4 +1,4 @@
-/*
+ /*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,14 +17,15 @@
// TODO(b/160821157): Replace FocusState with FocusState2.isFocused
@file:Suppress("DEPRECATION")
-package androidx.compose.foundation
+package androidx.compose.foundation.textfield
import android.os.Build
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.preferredSize
-import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index ef2bfeb..1e740d5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -26,9 +26,11 @@
import androidx.compose.ui.gesture.pressIndicatorGestureFilter
import androidx.compose.ui.gesture.tapGestureFilter
import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.onLongClick
+import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
/**
@@ -41,6 +43,8 @@
* @param enabled Controls the enabled state. When `false`, [onClick], [onLongClick] or
* [onDoubleClick] won't be invoked
* @param onClickLabel semantic / accessibility label for the [onClick] action
+ * @param role the type of user interface element. Accessibility services might use this
+ * to describe the element or do customizations
* @param interactionState [InteractionState] that will be updated when this Clickable is
* pressed, using [Interaction.Pressed]. Only initial (first) press will be recorded and added to
* [InteractionState]
@@ -56,6 +60,7 @@
fun Modifier.clickable(
enabled: Boolean = true,
onClickLabel: String? = null,
+ role: Role? = null,
interactionState: InteractionState = remember { InteractionState() },
indication: Indication? = AmbientIndication.current(),
onLongClickLabel: String? = null,
@@ -65,6 +70,9 @@
) = composed(
factory = {
val semanticModifier = Modifier.semantics(mergeDescendants = true) {
+ if (role != null) {
+ this.role = role
+ }
// b/156468846: add long click semantics and double click if needed
onClick(action = { onClick(); true }, label = onClickLabel)
if (onLongClick != null) {
@@ -112,6 +120,7 @@
name = "clickable"
properties["enabled"] = enabled
properties["onClickLabel"] = onClickLabel
+ properties["role"] = role
properties["onClick"] = onClick
properties["onDoubleClick"] = onDoubleClick
properties["onLongClick"] = onLongClick
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ProgressSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ProgressSemantics.kt
index 409e25d..d695b74 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ProgressSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ProgressSemantics.kt
@@ -22,27 +22,34 @@
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.semantics.stateDescriptionRange
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.util.annotation.FloatRange
+import androidx.compose.ui.util.annotation.IntRange
import androidx.compose.ui.util.format
import kotlin.math.roundToInt
/**
- * Contains the [semantics] required for a determinate progress indicator, that represents progress
- * ranging from 0.0 to 1.0. Values for [progress] outside of this range will be coerced into this
- * range.
+ * Contains the [semantics] required for a determinate progress indicator or the progress part of
+ * a slider, that represents progress within [valueRange]. [value] outside of this range will be
+ * coerced into this range.
*
* @sample androidx.compose.foundation.samples.DeterminateProgressSemanticsSample
*
- * @param progress The progress of this progress indicator, where 0.0 represents no progress and 1.0
- * represents full progress. If the value is outside of this range, it will be coerced into the
- * range.
+ * @param value current value of the ProgressIndicator/Slider. If outside of [valueRange] provided,
+ * value will be coerced to this range.
+ * @param valueRange range of values that value can take. Passed [value] will be coerced to this
+ * range
+ * @param steps if greater than 0, specifies the amounts of discrete values, evenly distributed
+ * between across the whole value range. If 0, any value from the range specified is allowed.
*/
@Stable
fun Modifier.progressSemantics(
- @FloatRange(from = 0.0, to = 1.0) progress: Float
+ value: Float,
+ valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
+ @IntRange(from = 0) steps: Int = 0
): Modifier {
- @Suppress("NAME_SHADOWING")
- val progress = progress.coerceIn(0f, 1f)
+ val progress = (
+ if (valueRange.endInclusive - valueRange.start == 0f) 0f
+ else (value - valueRange.start) / (valueRange.endInclusive - valueRange.start)
+ ).coerceIn(0f, 1f)
// We only display 0% or 100% when it is exactly 0% or 100%.
val percent = when (progress) {
@@ -53,7 +60,8 @@
return semantics {
stateDescription = Strings.TemplatePercent.format(percent)
- stateDescriptionRange = AccessibilityRangeInfo(progress, 0f..1f)
+ stateDescriptionRange =
+ AccessibilityRangeInfo(value.coerceIn(valueRange), valueRange, steps)
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Strings.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Strings.kt
index 27c432b..48879d2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Strings.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Strings.kt
@@ -18,14 +18,15 @@
// TODO(b/138327849): (STOPSHIP) Move all of these to resources once we have a real resources system,
// then delete this class
-@Suppress("MayBeConstant") // The compiler gets unhappy if these are const (b/138328700)
-object Strings {
- val Checked = "Checked"
- val Unchecked = "Unchecked"
- val Indeterminate = "Indeterminate"
- val Selected = "Selected"
- val NotSelected = "Not selected"
- val InProgress = "In progress"
- val TemplatePercent = "%d percent"
- val Toggle = "Toggle"
+internal object Strings {
+ const val Checked = "Checked"
+ const val Unchecked = "Unchecked"
+ const val On = "On"
+ const val Off = "Off"
+ const val Indeterminate = "Indeterminate"
+ const val Selected = "Selected"
+ const val NotSelected = "Not selected"
+ const val InProgress = "In progress"
+ const val TemplatePercent = "%d percent"
+ const val Toggle = "Toggle"
}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
index a6df531..fcc44f9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
@@ -28,6 +28,7 @@
import androidx.compose.ui.composed
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
@@ -45,8 +46,8 @@
* @param onClick callback to invoke when this item is clicked
* @param enabled whether or not this [selectable] will handle input events
* and appear enabled from a semantics perspective
- * @param inMutuallyExclusiveGroup whether or not this item is a part of mutually exclusive
- * group, meaning that only one of these items can be selected at any point of time
+ * @param role the type of user interface element. Accessibility services might use this
+ * to describe the element or do customizations
* @param interactionState [InteractionState] that will be updated when this element is
* pressed, using [Interaction.Pressed]
* @param indication indication to be shown when the modified element is pressed. By default,
@@ -58,7 +59,7 @@
fun Modifier.selectable(
selected: Boolean,
enabled: Boolean = true,
- inMutuallyExclusiveGroup: Boolean = true,
+ role: Role? = null,
interactionState: InteractionState = remember { InteractionState() },
indication: Indication? = AmbientIndication.current(),
onClick: () -> Unit
@@ -66,6 +67,7 @@
factory = {
Modifier.clickable(
enabled = enabled,
+ role = role,
interactionState = interactionState,
indication = indication,
onClick = onClick
@@ -78,7 +80,7 @@
name = "selectable"
properties["selected"] = selected
properties["enabled"] = enabled
- properties["inMutuallyExclusiveGroup"] = inMutuallyExclusiveGroup
+ properties["role"] = role
properties["interactionState"] = interactionState
properties["indication"] = indication
properties["onClick"] = onClick
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
index 002276e..6089f4d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
@@ -30,9 +30,11 @@
import androidx.compose.ui.gesture.pressIndicatorGestureFilter
import androidx.compose.ui.gesture.tapGestureFilter
import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.state.ToggleableState
@@ -52,6 +54,8 @@
* therefore the change of the state in requested.
* @param enabled whether or not this [toggleable] will handle input events and appear
* enabled for semantics purposes
+ * @param role the type of user interface element. Accessibility services might use this
+ * to describe the element or do customizations
* @param interactionState [InteractionState] that will be updated when this toggleable is
* pressed, using [Interaction.Pressed]
* @param indication indication to be shown when modified element is pressed. Be default,
@@ -63,6 +67,7 @@
fun Modifier.toggleable(
value: Boolean,
enabled: Boolean = true,
+ role: Role? = null,
interactionState: InteractionState = remember { InteractionState() },
indication: Indication? = AmbientIndication.current(),
onValueChange: (Boolean) -> Unit
@@ -71,6 +76,7 @@
name = "toggleable"
properties["value"] = value
properties["enabled"] = enabled
+ properties["role"] = role
properties["interactionState"] = interactionState
properties["indication"] = indication
properties["onValueChange"] = onValueChange
@@ -80,6 +86,7 @@
state = ToggleableState(value),
onClick = { onValueChange(!value) },
enabled = enabled,
+ role = role,
interactionState = interactionState,
indication = indication
)
@@ -101,6 +108,8 @@
* @param onClick will be called when user clicks the toggleable.
* @param enabled whether or not this [triStateToggleable] will handle input events and
* appear enabled for semantics purposes
+ * @param role the type of user interface element. Accessibility services might use this
+ * to describe the element or do customizations
* @param interactionState [InteractionState] that will be updated when this toggleable is
* pressed, using [Interaction.Pressed]
* @param indication indication to be shown when modified element is pressed. Be default,
@@ -112,6 +121,7 @@
fun Modifier.triStateToggleable(
state: ToggleableState,
enabled: Boolean = true,
+ role: Role? = null,
interactionState: InteractionState = remember { InteractionState() },
indication: Indication? = AmbientIndication.current(),
onClick: () -> Unit
@@ -120,27 +130,33 @@
name = "triStateToggleable"
properties["state"] = state
properties["enabled"] = enabled
+ properties["role"] = role
properties["interactionState"] = interactionState
properties["indication"] = indication
properties["onClick"] = onClick
},
- factory = { toggleableImpl(state, enabled, interactionState, indication, onClick) }
+ factory = {
+ toggleableImpl(state, enabled, role, interactionState, indication, onClick)
+ }
)
@Suppress("ModifierInspectorInfo")
private fun Modifier.toggleableImpl(
state: ToggleableState,
enabled: Boolean,
+ role: Role? = null,
interactionState: InteractionState,
indication: Indication?,
onClick: () -> Unit
): Modifier = composed {
// TODO(pavlis): Handle multiple states for Semantics
val semantics = Modifier.semantics(mergeDescendants = true) {
+ if (role != null) {
+ this.role = role
+ }
this.stateDescription = when (state) {
- // TODO(ryanmentley): These should be set by Checkbox, Switch, etc.
- On -> Strings.Checked
- Off -> Strings.Unchecked
+ On -> if (role == Role.Switch) Strings.On else Strings.Checked
+ Off -> if (role == Role.Switch) Strings.Off else Strings.Unchecked
Indeterminate -> Strings.Indeterminate
}
this.toggleableState = state
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
index cec507f..1c2db5d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
@@ -74,6 +74,11 @@
* @param onValueChange the callback that is triggered when the input service updates the text. An
* updated text comes as a parameter of the callback
* @param modifier optional [Modifier] for this text field.
+ * @param enabled controls the enabled state of the [BasicTextField]. When `false`, the text
+ * field will be neither editable nor focusable, the input of the text field will not be selectable
+ * @param readOnly controls the editable state of the [BasicTextField]. When `true`, the text
+ * fields will not be editable but otherwise operable. Read-only text fields are usually used to
+ * display the pre-filled text that user cannot edit
* @param textStyle Style configuration that applies at character level such as color, font etc.
* @param keyboardOptions software keyboard options that contains configuration such as
* [KeyboardType] and [ImeAction].
@@ -106,6 +111,8 @@
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ readOnly: Boolean = false,
textStyle: TextStyle = TextStyle.Default,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
singleLine: Boolean = false,
@@ -129,6 +136,8 @@
}
},
modifier = modifier,
+ enabled = enabled,
+ readOnly = readOnly,
textStyle = textStyle,
keyboardOptions = keyboardOptions,
maxLines = maxLines,
@@ -176,6 +185,11 @@
* [BasicTextField].
* @param onValueChange Called when the input service updates the values in [TextFieldValue].
* @param modifier optional [Modifier] for this text field.
+ * @param enabled controls the enabled state of the [BasicTextField]. When `false`, the text
+ * field will be neither editable nor focusable, the input of the text field will not be selectable
+ * @param readOnly controls the editable state of the [BasicTextField]. When `true`, the text
+ * fields will not be editable but otherwise operable. Read-only text fields are usually used to
+ * display the pre-filled text that user cannot edit
* @param textStyle Style configuration that applies at character level such as color, font etc.
* @param keyboardOptions software keyboard options that contains configuration such as
* [KeyboardType] and [ImeAction].
@@ -208,6 +222,8 @@
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ readOnly: Boolean = false,
textStyle: TextStyle = TextStyle.Default,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
singleLine: Boolean = false,
@@ -219,38 +235,58 @@
interactionState: InteractionState = remember { InteractionState() },
cursorColor: Color = Color.Black
) {
- // We use it to get the cursor position
- val textLayoutResult: Ref<TextLayoutResult?> = remember { Ref() }
-
val orientation = if (singleLine) Orientation.Horizontal else Orientation.Vertical
val scrollerPosition = rememberSavedInstanceState(saver = TextFieldScrollerPosition.Saver) {
TextFieldScrollerPosition()
}
- CoreTextField(
- value = value,
- onValueChange = onValueChange,
- textStyle = textStyle,
- onImeActionPerformed = onImeActionPerformed,
- visualTransformation = visualTransformation,
- onTextLayout = {
- textLayoutResult.value = it
- onTextLayout(it)
- },
- interactionState = interactionState,
- onTextInputStarted = onTextInputStarted,
- cursorColor = cursorColor,
- imeOptions = keyboardOptions.toImeOptions(singleLine = singleLine),
- softWrap = !singleLine,
- modifier = modifier
- .maxLinesHeight(if (singleLine) 1 else maxLines, textStyle)
- .textFieldScroll(
- orientation,
- scrollerPosition,
- value,
- visualTransformation,
- interactionState,
- textLayoutResult
- )
- )
+ // We use it to get the cursor position
+ val textLayoutResult: Ref<TextLayoutResult?> = remember { Ref() }
+
+ val textFieldModifier = modifier
+ .maxLinesHeight(if (singleLine) 1 else maxLines, textStyle)
+ .textFieldScroll(
+ orientation,
+ scrollerPosition,
+ value,
+ visualTransformation,
+ interactionState,
+ textLayoutResult,
+ enabled
+ )
+
+ if (enabled && !readOnly) {
+ CoreTextField(
+ value = value,
+ onValueChange = onValueChange,
+ textStyle = textStyle,
+ onImeActionPerformed = onImeActionPerformed,
+ visualTransformation = visualTransformation,
+ onTextLayout = {
+ textLayoutResult.value = it
+ onTextLayout(it)
+ },
+ interactionState = interactionState,
+ onTextInputStarted = onTextInputStarted,
+ cursorColor = cursorColor,
+ imeOptions = keyboardOptions.toImeOptions(singleLine = singleLine),
+ softWrap = !singleLine,
+ modifier = textFieldModifier
+ )
+ } else {
+ InactiveTextField(
+ value = value,
+ modifier = textFieldModifier,
+ enabled = enabled,
+ textStyle = textStyle,
+ singleLine = singleLine,
+ maxLines = maxLines,
+ visualTransformation = visualTransformation,
+ onTextLayout = {
+ textLayoutResult.value = it
+ onTextLayout(it)
+ },
+ interactionState = interactionState
+ )
+ }
}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index 7e3efd3..9604ecf 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -139,8 +139,8 @@
@InternalTextApi
fun CoreTextField(
value: TextFieldValue,
- modifier: Modifier = Modifier,
onValueChange: (TextFieldValue) -> Unit,
+ modifier: Modifier = Modifier,
textStyle: TextStyle = TextStyle.Default,
onImeActionPerformed: (ImeAction) -> Unit = {},
visualTransformation: VisualTransformation = VisualTransformation.None,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InactiveTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InactiveTextField.kt
new file mode 100644
index 0000000..fe4b034
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/InactiveTextField.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.foundation.text
+
+import androidx.compose.foundation.InteractionState
+import androidx.compose.foundation.focusable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.selection.DisableSelection
+import androidx.compose.ui.selection.SelectionContainer
+import androidx.compose.ui.semantics.disabled
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.input.VisualTransformation
+
+/**
+ * Implements disabled and readonly text field using Text.
+ */
+@Composable
+internal fun InactiveTextField(
+ value: TextFieldValue,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ textStyle: TextStyle = TextStyle.Default,
+ singleLine: Boolean = false,
+ maxLines: Int = Int.MAX_VALUE,
+ visualTransformation: VisualTransformation = VisualTransformation.None,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
+ interactionState: InteractionState? = null
+) {
+ val transformedText = remember(value, visualTransformation) {
+ visualTransformation.filter(AnnotatedString(value.text))
+ }.transformedText
+
+ val text: @Composable (Modifier) -> Unit = @Composable { textModifier ->
+ BasicText(
+ text = transformedText,
+ modifier = textModifier.semantics {
+ if (!enabled) disabled()
+ },
+ softWrap = !singleLine,
+ maxLines = if (singleLine) 1 else maxLines,
+ style = textStyle,
+ onTextLayout = onTextLayout
+ )
+ }
+ val textModifier = modifier.focusable(enabled, interactionState)
+ if (enabled) {
+ SelectionContainer(textModifier) {
+ text(Modifier)
+ }
+ } else {
+ DisableSelection {
+ text(textModifier)
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/MaxLinesHeightModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/MaxLinesHeightModifier.kt
index e37d940b..bec29cc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/MaxLinesHeightModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/MaxLinesHeightModifier.kt
@@ -32,7 +32,7 @@
* Constraint the height of the text field so that it vertically occupies no more than [maxLines]
* number of lines.
*/
-fun Modifier.maxLinesHeight(
+internal fun Modifier.maxLinesHeight(
@IntRange(from = 1) maxLines: Int,
textStyle: TextStyle
) = composed(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt
index 41b50c6..df4f65b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.gestures.rememberScrollableController
import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -55,7 +56,8 @@
textFieldValue: TextFieldValue,
visualTransformation: VisualTransformation,
interactionState: InteractionState,
- textLayoutResult: Ref<TextLayoutResult?>
+ textLayoutResult: Ref<TextLayoutResult?>,
+ enabled: Boolean = true
) = composed(
factory = {
// do not reverse direction only in case of RTL in horizontal orientation
@@ -77,7 +79,8 @@
orientation = orientation,
canScroll = { scrollerPosition.maximum != 0f },
reverseDirection = reverseDirection,
- controller = controller
+ controller = controller,
+ enabled = enabled
)
val cursorOffset = scrollerPosition.getOffsetToFollow(textFieldValue.selection)
@@ -108,6 +111,7 @@
name = "textFieldScroll"
properties["orientation"] = orientation
properties["scrollerPosition"] = scrollerPosition
+ properties["enabled"] = enabled
properties["textFieldValue"] = textFieldValue
properties["visualTransformation"] = visualTransformation
properties["interactionState"] = interactionState
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
index 0384857..44ceee1 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
@@ -16,7 +16,7 @@
package androidx.compose.foundation
-import androidx.compose.animation.animate
+import androidx.compose.animation.animateAsState
import androidx.compose.animation.core.TweenSpec
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.lazy.LazyListState
@@ -218,9 +218,9 @@
}
}
- val color = animate(
+ val color by animateAsState(
if (isHover) style.hoverColor else style.unhoverColor,
- animSpec = TweenSpec(durationMillis = style.hoverDurationMillis)
+ animationSpec = TweenSpec(durationMillis = style.hoverDurationMillis)
)
val isVisible = sliderAdapter.size < containerSize
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index b33ea77..db130d2 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -526,8 +526,8 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-9IHodII(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-IIju55g(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 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField-GDNbQiw(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField-TM4hwe4(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
}
@Deprecated public final class ProgressIndicatorConstants {
@@ -840,8 +840,8 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField-Y71GaAQ(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 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
- method @androidx.compose.runtime.Composable public static void TextField-f_2i414(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void TextField-PrKp87A(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void TextField-mP9nhjw(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
field public static final float ContainerAlpha = 0.12f;
}
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index b33ea77..db130d2 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -526,8 +526,8 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-9IHodII(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-IIju55g(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 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField-GDNbQiw(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField-TM4hwe4(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
}
@Deprecated public final class ProgressIndicatorConstants {
@@ -840,8 +840,8 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField-Y71GaAQ(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 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
- method @androidx.compose.runtime.Composable public static void TextField-f_2i414(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void TextField-PrKp87A(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void TextField-mP9nhjw(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
field public static final float ContainerAlpha = 0.12f;
}
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index b33ea77..db130d2 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -526,8 +526,8 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-9IHodII(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-IIju55g(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 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField-GDNbQiw(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField-TM4hwe4(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor);
}
@Deprecated public final class ProgressIndicatorConstants {
@@ -840,8 +840,8 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField-Y71GaAQ(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 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
- method @androidx.compose.runtime.Composable public static void TextField-f_2i414(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, 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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void TextField-PrKp87A(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Composable public static void TextField-mP9nhjw(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 boolean isErrorValue, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional boolean singleLine, optional int maxLines, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted, optional androidx.compose.foundation.InteractionState interactionState, optional long activeColor, optional long inactiveColor, optional long errorColor, optional long backgroundColor, optional androidx.compose.ui.graphics.Shape shape);
field public static final float ContainerAlpha = 0.12f;
}
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
index d765481..2c9263c 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/ButtonDemo.kt
@@ -16,7 +16,7 @@
package androidx.compose.material.demos
-import androidx.compose.animation.animate
+import androidx.compose.animation.animateAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ScrollableColumn
import androidx.compose.foundation.layout.Arrangement
@@ -162,7 +162,7 @@
var checked by remember { mutableStateOf(false) }
IconToggleButton(checked = checked, enabled = false, onCheckedChange = { checked = it }) {
- val tint = animate(if (checked) Color(0xFFEC407A) else Color(0xFFB0BEC5))
+ val tint by animateAsState(if (checked) Color(0xFFEC407A) else Color(0xFFB0BEC5))
Icon(Icons.Filled.Favorite, tint = tint)
}
}
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
index 92121a8..36f4396 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialTextField.kt
@@ -65,9 +65,8 @@
@Composable
fun TextFieldsDemo() {
- ScrollableColumn(
- modifier = Modifier.fillMaxHeight(),
- contentPadding = PaddingValues(10.dp)
+ Column(
+ modifier = Modifier.fillMaxHeight()
) {
Text("Password text field")
PasswordTextField()
@@ -141,6 +140,8 @@
var singleLineChecked by savedInstanceState { true }
var selectedOption by savedInstanceState { Option.None }
var selectedTextField by savedInstanceState { TextFieldType.Filled }
+ var disabled by savedInstanceState { false }
+ var readOnly by savedInstanceState { false }
val textField: @Composable () -> Unit = @Composable {
when (selectedTextField) {
@@ -148,6 +149,8 @@
TextField(
value = text,
onValueChange = { text = it },
+ enabled = !disabled,
+ readOnly = readOnly,
singleLine = singleLineChecked,
label = {
val label =
@@ -163,6 +166,8 @@
OutlinedTextField(
value = text,
onValueChange = { text = it },
+ enabled = !disabled,
+ readOnly = readOnly,
singleLine = singleLineChecked,
label = {
val label =
@@ -187,29 +192,27 @@
Column {
Title("Text field type")
- Column {
- TextFieldType.values().map { it.name }.forEach { textType ->
- Row(
- Modifier
- .fillMaxWidth()
- .selectable(
- selected = (textType == selectedTextField.name),
- onClick = {
- selectedTextField = TextFieldType.valueOf(textType)
- }
- )
- .padding(horizontal = 16.dp)
- ) {
- RadioButton(
+ TextFieldType.values().map { it.name }.forEach { textType ->
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .selectable(
selected = (textType == selectedTextField.name),
- onClick = { selectedTextField = TextFieldType.valueOf(textType) }
+ onClick = {
+ selectedTextField = TextFieldType.valueOf(textType)
+ }
)
- Text(
- text = textType,
- style = MaterialTheme.typography.body1.merge(),
- modifier = Modifier.padding(start = 16.dp)
- )
- }
+ .padding(horizontal = 16.dp)
+ ) {
+ RadioButton(
+ selected = (textType == selectedTextField.name),
+ onClick = { selectedTextField = TextFieldType.valueOf(textType) }
+ )
+ Text(
+ text = textType,
+ style = MaterialTheme.typography.body1.merge(),
+ modifier = Modifier.padding(start = 16.dp)
+ )
}
}
@@ -239,29 +242,39 @@
Spacer(Modifier.preferredHeight(20.dp))
Title("Assistive text")
- Column {
- Option.values().map { it.name }.forEach { text ->
- Row(
- Modifier
- .fillMaxWidth()
- .selectable(
- selected = (text == selectedOption.name),
- onClick = { selectedOption = Option.valueOf(text) }
- )
- .padding(horizontal = 16.dp)
- ) {
- RadioButton(
+ Option.values().map { it.name }.forEach { text ->
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .selectable(
selected = (text == selectedOption.name),
onClick = { selectedOption = Option.valueOf(text) }
)
- Text(
- text = text,
- style = MaterialTheme.typography.body1.merge(),
- modifier = Modifier.padding(start = 16.dp)
- )
- }
+ .padding(horizontal = 16.dp)
+ ) {
+ RadioButton(
+ selected = (text == selectedOption.name),
+ onClick = { selectedOption = Option.valueOf(text) }
+ )
+ Text(
+ text = text,
+ style = MaterialTheme.typography.body1.merge(),
+ modifier = Modifier.padding(start = 16.dp)
+ )
}
}
+
+ Title("Other settings")
+ OptionRow(
+ title = "Read-only",
+ checked = readOnly,
+ onCheckedChange = { readOnly = it }
+ )
+ OptionRow(
+ title = "Disabled",
+ checked = disabled,
+ onCheckedChange = { disabled = it }
+ )
}
}
}
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/IconButtonSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/IconButtonSamples.kt
index a50a19c..84ac071 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/IconButtonSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/IconButtonSamples.kt
@@ -17,7 +17,7 @@
package androidx.compose.material.samples
import androidx.annotation.Sampled
-import androidx.compose.animation.animate
+import androidx.compose.animation.animateAsState
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.IconToggleButton
@@ -44,7 +44,7 @@
var checked by remember { mutableStateOf(false) }
IconToggleButton(checked = checked, onCheckedChange = { checked = it }) {
- val tint = animate(if (checked) Color(0xFFEC407A) else Color(0xFFB0BEC5))
+ val tint by animateAsState(if (checked) Color(0xFFEC407A) else Color(0xFFB0BEC5))
Icon(Icons.Filled.Favorite, tint = tint)
}
}
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt
index ab3277e..6fb006f 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/ProgressIndicatorSamples.kt
@@ -17,7 +17,7 @@
package androidx.compose.material.samples
import androidx.annotation.Sampled
-import androidx.compose.animation.animate
+import androidx.compose.animation.core.animateAsState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
@@ -39,9 +39,9 @@
@Composable
fun LinearProgressIndicatorSample() {
var progress by remember { mutableStateOf(0.1f) }
- val animatedProgress = animate(
- target = progress,
- animSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
+ val animatedProgress by animateAsState(
+ targetValue = progress,
+ animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
@@ -61,9 +61,9 @@
@Composable
fun CircularProgressIndicatorSample() {
var progress by remember { mutableStateOf(0.1f) }
- val animatedProgress = animate(
- target = progress,
- animSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
+ val animatedProgress by animateAsState(
+ targetValue = progress,
+ animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt
index ca1b39d..87bb647 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt
@@ -17,7 +17,8 @@
package androidx.compose.material.samples
import androidx.annotation.Sampled
-import androidx.compose.animation.animate
+import androidx.compose.animation.animateAsState
+import androidx.compose.animation.core.animateAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -96,7 +97,7 @@
},
background = {
val direction = dismissState.dismissDirection ?: return@SwipeToDismiss
- val color = animate(
+ val color by animateAsState(
when (dismissState.targetValue) {
Default -> Color.LightGray
DismissedToEnd -> Color.Green
@@ -111,7 +112,9 @@
StartToEnd -> Icons.Default.Done
EndToStart -> Icons.Default.Delete
}
- val scale = animate(if (dismissState.targetValue == Default) 0.75f else 1f)
+ val scale by animateAsState(
+ if (dismissState.targetValue == Default) 0.75f else 1f
+ )
Box(
Modifier.fillMaxSize().background(color).padding(horizontal = 20.dp),
@@ -122,9 +125,9 @@
},
dismissContent = {
Card(
- elevation = animate(
+ elevation = animateAsState(
if (dismissState.dismissDirection != null) 4.dp else 0.dp
- )
+ ).value
) {
ListItem(
text = {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonTest.kt
index faa4f03..a8745fb 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonTest.kt
@@ -46,6 +46,10 @@
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertHeightIsAtLeast
import androidx.compose.ui.test.assertHeightIsEqualTo
@@ -89,6 +93,7 @@
}
rule.onNodeWithTag("myButton")
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
.assertIsEnabled()
}
@@ -103,6 +108,7 @@
}
rule.onNodeWithTag("myButton")
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
.assertIsNotEnabled()
}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxUiTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxUiTest.kt
index 8df6dc8..12767ff 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxUiTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxUiTest.kt
@@ -15,17 +15,20 @@
*/
package androidx.compose.material
-import androidx.compose.foundation.Strings
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.state.ToggleableState.Indeterminate
import androidx.compose.ui.state.ToggleableState.Off
import androidx.compose.ui.state.ToggleableState.On
import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
@@ -59,14 +62,16 @@
}
rule.onNodeWithTag("checkboxUnchecked")
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Checkbox))
.assertIsEnabled()
.assertIsOff()
- .assertValueEquals(Strings.Unchecked)
+ .assertValueEquals("Unchecked")
rule.onNodeWithTag("checkboxChecked")
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Checkbox))
.assertIsEnabled()
.assertIsOn()
- .assertValueEquals(Strings.Checked)
+ .assertValueEquals("Checked")
}
@Test
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ProgressIndicatorTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ProgressIndicatorTest.kt
index 7f8b8ab..5514d9d 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ProgressIndicatorTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ProgressIndicatorTest.kt
@@ -15,7 +15,6 @@
*/
package androidx.compose.material
-import androidx.compose.foundation.Strings
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
@@ -90,7 +89,7 @@
}
rule.onNodeWithTag(tag)
- .assertValueEquals(Strings.InProgress)
+ .assertValueEquals("In progress")
}
@Test
@@ -150,7 +149,7 @@
}
rule.onNodeWithTag(tag)
- .assertValueEquals(Strings.InProgress)
+ .assertValueEquals("In progress")
}
@Test
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonTest.kt
index b5cabda..38d3c90 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonTest.kt
@@ -16,12 +16,15 @@
package androidx.compose.material
-import androidx.compose.foundation.Strings
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.assertValueEquals
@@ -48,10 +51,10 @@
private val itemThree = "Sap"
private fun SemanticsNodeInteraction.assertHasSelectedSemantics(): SemanticsNodeInteraction =
- assertIsSelected().assertValueEquals(Strings.Selected)
+ assertIsSelected().assertValueEquals("Selected")
private fun SemanticsNodeInteraction.assertHasUnSelectedSemantics(): SemanticsNodeInteraction =
- assertIsNotSelected().assertValueEquals(Strings.NotSelected)
+ assertIsNotSelected().assertValueEquals("Not selected")
private val options = listOf(itemOne, itemTwo, itemThree)
@@ -71,9 +74,21 @@
}
}
- rule.onNodeWithTag(itemOne).assertHasSelectedSemantics()
- rule.onNodeWithTag(itemTwo).assertHasUnSelectedSemantics()
- rule.onNodeWithTag(itemThree).assertHasUnSelectedSemantics()
+ rule.onNodeWithTag(itemOne)
+ .assert(
+ SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.RadioButton)
+ )
+ .assertHasSelectedSemantics()
+ rule.onNodeWithTag(itemTwo)
+ .assertHasUnSelectedSemantics()
+ .assert(
+ SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.RadioButton)
+ )
+ rule.onNodeWithTag(itemThree)
+ .assert(
+ SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.RadioButton)
+ )
+ .assertHasUnSelectedSemantics()
}
@Test
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchTest.kt
index 9043a7c..330f3ba 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchTest.kt
@@ -16,7 +16,6 @@
package androidx.compose.material
-import androidx.compose.foundation.Strings
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Providers
@@ -26,6 +25,10 @@
import androidx.compose.ui.platform.AmbientLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsOff
@@ -70,13 +73,15 @@
}
rule.onNodeWithTag("checked")
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Switch))
.assertIsEnabled()
.assertIsOn()
- .assertValueEquals(Strings.Checked)
+ .assertValueEquals("On")
rule.onNodeWithTag("unchecked")
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Switch))
.assertIsEnabled()
.assertIsOff()
- .assertValueEquals(Strings.Unchecked)
+ .assertValueEquals("Off")
}
@Test
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
index bd3d988..65e2389 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
@@ -34,6 +34,10 @@
import androidx.compose.ui.platform.InspectableValue
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsEqualTo
@@ -80,6 +84,24 @@
}
@Test
+ fun defaultSemantics() {
+ rule.setMaterialContent {
+ Box {
+ Tab(
+ text = { Text("Text") },
+ modifier = Modifier.testTag("tab"),
+ selected = true,
+ onClick = {}
+ )
+ }
+ }
+
+ rule.onNodeWithTag("tab")
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Tab))
+ .assertIsSelected()
+ }
+
+ @Test
fun textTab_height() {
rule
.setMaterialContentForSizeAssertions {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
index e2a4cc5..285e619 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
@@ -19,6 +19,7 @@
import android.os.Build
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
import androidx.compose.material.AmbientContentColor
import androidx.compose.material.GOLDEN_MATERIAL
import androidx.compose.material.OutlinedTextField
@@ -31,6 +32,7 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.center
import androidx.compose.ui.test.down
@@ -38,7 +40,9 @@
import androidx.compose.ui.test.move
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performGesture
+import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.up
+import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -55,6 +59,14 @@
class OutlinedTextFieldScreenshotTest {
private val TextFieldTag = "OutlinedTextField"
+ private val longText = TextFieldValue(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam," +
+ " quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
+ "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
+ "fugiat nulla pariatur."
+ )
+
@get:Rule
val rule = createComposeRule()
@@ -103,10 +115,7 @@
}
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("outlined_textField_focused")
}
@@ -125,10 +134,7 @@
}
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("outlined_textField_focused_rtl")
}
@@ -146,10 +152,7 @@
}
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("outlined_textField_focused_errorState")
}
@@ -224,10 +227,7 @@
)
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("outlined_textField_multiLine_withLabel_placeholderAlignedToTop")
}
@@ -243,10 +243,7 @@
)
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("outlined_textField_multiLine_withoutLabel_placeholderAlignedToTop")
}
@@ -307,10 +304,7 @@
)
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("outlined_textField_singleLine_withLabel_placeholderAlignedToTop")
}
@@ -327,10 +321,7 @@
)
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden(
"outlined_textField_singleLine_withoutLabel_placeholderCenteredVertically"
@@ -351,6 +342,114 @@
assertAgainstGolden("outlined_textField_singleLine_labelCenteredVetically")
}
+ @Test
+ fun outlinedTextField_disabled() {
+ rule.setMaterialContent {
+ Box(Modifier.semantics(mergeDescendants = true) {}.testTag(TextFieldTag)) {
+ OutlinedTextField(
+ value = TextFieldValue("Text"),
+ onValueChange = {},
+ singleLine = true,
+ enabled = false
+ )
+ }
+ }
+
+ assertAgainstGolden("outlinedTextField_disabled")
+ }
+
+ @Test
+ fun outlinedTextField_disabled_notFocusable() {
+ rule.setMaterialContent {
+ Box(Modifier.semantics(mergeDescendants = true) {}.testTag(TextFieldTag)) {
+ OutlinedTextField(
+ value = TextFieldValue("Text"),
+ onValueChange = {},
+ singleLine = true,
+ enabled = false
+ )
+ }
+ }
+
+ rule.onNodeWithTag(TextFieldTag).focus()
+
+ assertAgainstGolden("outlinedTextField_disabled_notFocusable")
+ }
+
+ @Test
+ fun outlinedTextField_disabled_notScrolled() {
+ rule.setMaterialContent {
+ Box(Modifier.semantics(mergeDescendants = true) {}.testTag(TextFieldTag)) {
+ OutlinedTextField(
+ value = longText,
+ onValueChange = { },
+ singleLine = true,
+ modifier = Modifier.width(300.dp),
+ enabled = false
+ )
+ }
+ }
+
+ rule.onNodeWithTag(TextFieldTag).performGesture { swipeLeft() }
+
+ assertAgainstGolden("outlinedTextField_disabled_notScrolled")
+ }
+
+ @Test
+ fun outlinedTextField_readOnly() {
+ rule.setMaterialContent {
+ OutlinedTextField(
+ value = TextFieldValue("Text"),
+ onValueChange = {},
+ modifier = Modifier.testTag(TextFieldTag),
+ enabled = true,
+ readOnly = true
+ )
+ }
+
+ assertAgainstGolden("outlinedTextField_readOnly")
+ }
+
+ @Test
+ fun outlinedTextField_readOnly_focused() {
+ rule.setMaterialContent {
+ OutlinedTextField(
+ value = TextFieldValue("Text"),
+ onValueChange = {},
+ modifier = Modifier.testTag(TextFieldTag),
+ enabled = true,
+ readOnly = true
+ )
+ }
+
+ rule.onNodeWithTag(TextFieldTag).focus()
+
+ assertAgainstGolden("outlinedTextField_readOnly_focused")
+ }
+
+ @Test
+ fun outlinedTextField_readOnly_scrolled() {
+ rule.setMaterialContent {
+ OutlinedTextField(
+ value = longText,
+ onValueChange = { },
+ modifier = Modifier.testTag(TextFieldTag).width(300.dp),
+ singleLine = true,
+ enabled = true,
+ readOnly = true
+ )
+ }
+
+ rule.onNodeWithTag(TextFieldTag).performGesture { swipeLeft() }
+
+ assertAgainstGolden("outlinedTextField_readOnly_scrolled")
+ }
+
+ private fun SemanticsNodeInteraction.focus() {
+ // split click into (down) and (move, up) to enforce a composition in between
+ this.performGesture { down(center) }.performGesture { move(); up() }
+ }
+
private fun assertAgainstGolden(goldenIdentifier: String) {
rule.onNodeWithTag(TextFieldTag)
.captureToImage()
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt
index c870083..96c7d79 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt
@@ -19,6 +19,7 @@
import android.os.Build
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
import androidx.compose.material.AmbientContentColor
import androidx.compose.material.GOLDEN_MATERIAL
import androidx.compose.material.Text
@@ -31,6 +32,7 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.center
import androidx.compose.ui.test.down
@@ -38,7 +40,9 @@
import androidx.compose.ui.test.move
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performGesture
+import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.up
+import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -54,6 +58,13 @@
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
class TextFieldScreenshotTest {
private val TextFieldTag = "TextField"
+ private val longText = TextFieldValue(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam," +
+ " quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
+ "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
+ "fugiat nulla pariatur."
+ )
@get:Rule
val rule = createComposeRule()
@@ -103,10 +114,7 @@
}
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("filled_textField_focused")
}
@@ -125,10 +133,7 @@
}
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("filled_textField_focused_rtl")
}
@@ -145,10 +150,7 @@
)
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("filled_textField_focused_errorState")
}
@@ -222,10 +224,7 @@
)
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("filled_textField_multiLine_withLabel_placeholderAlignedToTop")
}
@@ -241,10 +240,7 @@
)
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("filled_textField_multiLine_withoutLabel_placeholderAlignedToTop")
}
@@ -305,10 +301,7 @@
)
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden("filled_textField_singleLine_withLabel_placeholderAlignedToTop")
}
@@ -325,10 +318,7 @@
)
}
- rule.onNodeWithTag(TextFieldTag)
- // split click into (down) and (move, up) to enforce a composition in between
- .performGesture { down(center) }
- .performGesture { move(); up() }
+ rule.onNodeWithTag(TextFieldTag).focus()
assertAgainstGolden(
"filled_textField_singleLine_withoutLabel_placeholderCenteredVertically"
@@ -349,6 +339,110 @@
assertAgainstGolden("filled_textField_singleLine_labelCenteredVetically")
}
+ @Test
+ fun textField_disabled() {
+ rule.setContent {
+ TextField(
+ value = TextFieldValue("Text"),
+ onValueChange = {},
+ modifier = Modifier.testTag(TextFieldTag),
+ singleLine = true,
+ enabled = false
+ )
+ }
+
+ assertAgainstGolden("textField_disabled")
+ }
+
+ @Test
+ fun textField_disabled_notFocusable() {
+ rule.setContent {
+ TextField(
+ value = TextFieldValue("Text"),
+ onValueChange = {},
+ singleLine = true,
+ modifier = Modifier.testTag(TextFieldTag),
+ enabled = false
+ )
+ }
+
+ rule.onNodeWithTag(TextFieldTag).focus()
+
+ assertAgainstGolden("textField_disabled_notFocusable")
+ }
+
+ @Test
+ fun textField_disabled_notScrolled() {
+ rule.setContent {
+ TextField(
+ value = longText,
+ onValueChange = { },
+ singleLine = true,
+ modifier = Modifier.testTag(TextFieldTag).width(300.dp),
+ enabled = false
+ )
+ }
+
+ rule.onNodeWithTag(TextFieldTag).performGesture { swipeLeft() }
+
+ assertAgainstGolden("textField_disabled_notScrolled")
+ }
+
+ @Test
+ fun textField_readOnly() {
+ rule.setContent {
+ TextField(
+ value = TextFieldValue("Text"),
+ onValueChange = {},
+ modifier = Modifier.testTag(TextFieldTag),
+ enabled = true,
+ readOnly = true
+ )
+ }
+
+ assertAgainstGolden("textField_readOnly")
+ }
+
+ @Test
+ fun textField_readOnly_focused() {
+ rule.setContent {
+ TextField(
+ value = TextFieldValue("Text"),
+ onValueChange = {},
+ modifier = Modifier.testTag(TextFieldTag),
+ enabled = true,
+ readOnly = true
+ )
+ }
+
+ rule.onNodeWithTag(TextFieldTag).focus()
+
+ assertAgainstGolden("textField_readOnly_focused")
+ }
+
+ @Test
+ fun textField_readOnly_scrolled() {
+ rule.setContent {
+ TextField(
+ value = longText,
+ onValueChange = { },
+ modifier = Modifier.testTag(TextFieldTag).width(300.dp),
+ singleLine = true,
+ enabled = true,
+ readOnly = true
+ )
+ }
+
+ rule.onNodeWithTag(TextFieldTag).performGesture { swipeLeft() }
+
+ assertAgainstGolden("textField_readOnly_scrolled")
+ }
+
+ private fun SemanticsNodeInteraction.focus() {
+ // split click into (down) and (move, up) to enforce a composition in between
+ this.performGesture { down(center) }.performGesture { move(); up() }
+ }
+
private fun assertAgainstGolden(goldenIdentifier: String) {
rule.onNodeWithTag(TextFieldTag)
.captureToImage()
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
index 682db4a..4aa9d9e 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
@@ -16,12 +16,12 @@
package androidx.compose.material
-import androidx.compose.animation.animate
import androidx.compose.animation.asDisposableClock
import androidx.compose.animation.core.AnimationClockObservable
import androidx.compose.animation.core.AnimationEndReason.Interrupted
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.animateAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -33,6 +33,7 @@
import androidx.compose.material.BackdropValue.Revealed
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.savedinstancestate.Saver
import androidx.compose.runtime.savedinstancestate.rememberSavedInstanceState
@@ -377,7 +378,10 @@
visible: Boolean
) {
if (color != Color.Transparent) {
- val alpha = animate(target = if (visible) 1f else 0f, animSpec = TweenSpec())
+ val alpha by animateAsState(
+ targetValue = if (visible) 1f else 0f,
+ animationSpec = TweenSpec()
+ )
val dismissModifier = if (visible) Modifier.tapGestureFilter { onDismiss() } else Modifier
Canvas(
@@ -403,8 +407,8 @@
) {
// The progress of the animation between Revealed (0) and Concealed (2).
// The midpoint (1) is the point where the appBar and backContent are switched.
- val animationProgress = animate(
- target = if (target == Revealed) 0f else 2f, animSpec = TweenSpec()
+ val animationProgress by animateAsState(
+ targetValue = if (target == Revealed) 0f else 2f, animationSpec = TweenSpec()
)
val animationSlideOffset = with(AmbientDensity.current) { AnimationSlideOffset.toPx() }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt
index 30d12b7..83a5f0b 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt
@@ -16,10 +16,10 @@
package androidx.compose.material
-import androidx.compose.animation.animate
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.VectorizedAnimationSpec
+import androidx.compose.animation.core.animateAsState
import androidx.compose.foundation.Interaction
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.layout.Arrangement
@@ -34,6 +34,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Providers
import androidx.compose.runtime.emptyContent
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -204,9 +205,9 @@
selected: Boolean,
content: @Composable (animationProgress: Float) -> Unit
) {
- val animationProgress = animate(
- target = if (selected) 1f else 0f,
- animSpec = BottomNavigationAnimationSpec
+ val animationProgress by animateAsState(
+ targetValue = if (selected) 1f else 0f,
+ animationSpec = BottomNavigationAnimationSpec
)
val color = lerp(inactiveColor, activeColor, animationProgress)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
index b23e6fe..d82ede6 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
@@ -46,6 +46,7 @@
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.AmbientAnimationClock
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -117,6 +118,7 @@
modifier = modifier.clickable(
onClick = onClick,
enabled = enabled,
+ role = Role.Button,
interactionState = interactionState,
indication = null
)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
index 811d694..e16def2 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
@@ -53,6 +53,7 @@
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.AmbientAnimationClock
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
@@ -140,6 +141,7 @@
state = state,
onClick = onClick,
enabled = enabled,
+ role = Role.Checkbox,
interactionState = interactionState,
indication = rememberRipple(
bounded = false,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
index 40fcf2b..fe931083 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
@@ -43,6 +43,7 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.AmbientAnimationClock
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -88,6 +89,7 @@
Surface(
modifier = modifier.clickable(
onClick = onClick,
+ role = Role.Button,
interactionState = interactionState,
indication = null
),
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/IconButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/IconButton.kt
index c2b3ad0..e8ceb14 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/IconButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/IconButton.kt
@@ -27,6 +27,7 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
/**
@@ -67,6 +68,7 @@
.clickable(
onClick = onClick,
enabled = enabled,
+ role = Role.Button,
interactionState = interactionState,
indication = rememberRipple(bounded = false, radius = RippleRadius)
)
@@ -107,6 +109,7 @@
value = checked,
onValueChange = onCheckedChange,
enabled = enabled,
+ role = Role.Checkbox,
interactionState = interactionState,
indication = rememberRipple(bounded = false, radius = RippleRadius)
).then(IconButtonSizeModifier),
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index 08925b9..ae32700 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -16,12 +16,12 @@
package androidx.compose.material
-import androidx.compose.animation.animate
import androidx.compose.animation.asDisposableClock
import androidx.compose.animation.core.AnimationClockObservable
import androidx.compose.animation.core.AnimationEndReason
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.animateAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -30,6 +30,7 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.savedinstancestate.Saver
import androidx.compose.runtime.savedinstancestate.rememberSavedInstanceState
@@ -286,7 +287,10 @@
visible: Boolean
) {
if (color != Color.Transparent) {
- val alpha = animate(target = if (visible) 1f else 0f, animSpec = TweenSpec())
+ val alpha by animateAsState(
+ targetValue = if (visible) 1f else 0f,
+ animationSpec = TweenSpec()
+ )
val dismissModifier = if (visible) Modifier.tapGestureFilter { onDismiss() } else Modifier
Canvas(
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
index ee1eceb..06df31a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
@@ -66,6 +66,12 @@
* @param onValueChange the callback that is triggered when the input service updates the text. An
* updated text comes as a parameter of the callback
* @param modifier a [Modifier] for this text field
+ * @param enabled controls the enabled state of the [OutlinedTextField]. When `false`, the text field will
+ * be neither editable nor focusable, the input of the text field will not be selectable,
+ * visually text field will appear in the disabled UI state
+ * @param readOnly controls the editable state of the [OutlinedTextField]. When `true`, the text
+ * fields will not be editable but otherwise operable. Read-only text fields are usually used to
+ * display the pre-filled text that user cannot edit
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [AmbientTextStyle] defined by the theme
* @param label the optional label to be displayed inside the text field container. The default
@@ -115,6 +121,8 @@
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ readOnly: Boolean = false,
textStyle: TextStyle = AmbientTextStyle.current,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
@@ -137,6 +145,8 @@
TextFieldImpl(
type = TextFieldType.Outlined,
+ enabled = enabled,
+ readOnly = readOnly,
value = textFieldValue,
onValueChange = {
textFieldValueState = it
@@ -181,6 +191,12 @@
* @param onValueChange the callback that is triggered when the input service updates values in
* [TextFieldValue]. An updated [TextFieldValue] comes as a parameter of the callback
* @param modifier a [Modifier] for this text field
+ * @param enabled controls the enabled state of the [OutlinedTextField]. When `false`, the text field will
+ * be neither editable nor focusable, the input of the text field will not be selectable,
+ * visually text field will appear in the disabled UI state
+ * @param readOnly controls the editable state of the [OutlinedTextField]. When `true`, the text
+ * fields will not be editable but otherwise operable. Read-only text fields are usually used to
+ * display the pre-filled text that user cannot edit
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [AmbientTextStyle] defined by the theme
* @param label the optional label to be displayed inside the text field container. The default
@@ -230,6 +246,8 @@
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ readOnly: Boolean = false,
textStyle: TextStyle = AmbientTextStyle.current,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
@@ -249,6 +267,8 @@
) {
TextFieldImpl(
type = TextFieldType.Outlined,
+ enabled = enabled,
+ readOnly = readOnly,
value = value,
onValueChange = onValueChange,
modifier = modifier,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
index 61ba138..2e66236 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/RadioButton.kt
@@ -43,6 +43,7 @@
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.AmbientAnimationClock
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -78,6 +79,7 @@
interactionState: InteractionState = remember { InteractionState() },
colors: RadioButtonColors = RadioButtonDefaults.colors()
) {
+ @Suppress("Deprecation") // b/176192329
val dotRadius = animate(
target = if (selected) RadioButtonDotSize / 2 else 0.dp,
animSpec = tween(durationMillis = RadioAnimationDuration)
@@ -88,6 +90,7 @@
selected = selected,
onClick = onClick,
enabled = enabled,
+ role = Role.RadioButton,
interactionState = interactionState,
indication = rememberRipple(
bounded = false,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
index c6b155b..0c05a50 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
@@ -25,7 +25,6 @@
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Interaction
import androidx.compose.foundation.InteractionState
-import androidx.compose.foundation.Strings
import androidx.compose.foundation.animation.FlingConfig
import androidx.compose.foundation.animation.defaultFlingConfig
import androidx.compose.foundation.animation.fling
@@ -39,6 +38,7 @@
import androidx.compose.foundation.layout.preferredHeightIn
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.layout.preferredWidthIn
+import androidx.compose.foundation.progressSemantics
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.SliderDefaults.InactiveTrackColorAlpha
import androidx.compose.material.SliderDefaults.TickColorAlpha
@@ -58,18 +58,13 @@
import androidx.compose.ui.platform.AmbientAnimationClock
import androidx.compose.ui.platform.AmbientDensity
import androidx.compose.ui.platform.AmbientLayoutDirection
-import androidx.compose.ui.semantics.AccessibilityRangeInfo
-import androidx.compose.ui.semantics.stateDescription
-import androidx.compose.ui.semantics.stateDescriptionRange
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.setProgress
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.annotation.IntRange
-import androidx.compose.ui.util.format
import androidx.compose.ui.util.lerp
import kotlin.math.abs
-import kotlin.math.roundToInt
/**
* Sliders allow users to make selections from a range of values.
@@ -375,16 +370,7 @@
@IntRange(from = 0) steps: Int = 0
): Modifier {
val coerced = value.coerceIn(position.startValue, position.endValue)
- val fraction = calcFraction(position.startValue, position.endValue, coerced)
- // We only display 0% or 100% when it is exactly 0% or 100%.
- val percent = when (fraction) {
- 0f -> 0
- 1f -> 100
- else -> (fraction * 100).roundToInt().coerceIn(1, 99)
- }
return semantics(mergeDescendants = true) {
- stateDescription = Strings.TemplatePercent.format(percent)
- stateDescriptionRange = AccessibilityRangeInfo(coerced, valueRange, steps)
setProgress(
action = { targetValue ->
val newValue = targetValue.coerceIn(position.startValue, position.endValue)
@@ -405,7 +391,7 @@
}
}
)
- }
+ }.progressSemantics(value, valueRange, steps)
}
/**
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
index fc9dbcd..3146d9a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Switch.kt
@@ -47,6 +47,7 @@
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.platform.AmbientDensity
import androidx.compose.ui.platform.AmbientLayoutDirection
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
@@ -89,6 +90,7 @@
value = checked,
onValueChange = onCheckedChange,
enabled = enabled,
+ role = Role.Switch,
interactionState = interactionState,
indication = null
)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
index 8d4ed29..976e4ee 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Tab.kt
@@ -17,9 +17,9 @@
package androidx.compose.material
import androidx.compose.animation.ColorPropKey
-import androidx.compose.animation.animate
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.animateAsState
import androidx.compose.animation.core.transitionDefinition
import androidx.compose.animation.core.tween
import androidx.compose.animation.transition
@@ -41,6 +41,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Providers
import androidx.compose.runtime.emptyContent
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -52,6 +53,7 @@
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
@@ -151,6 +153,7 @@
.selectable(
selected = selected,
onClick = onClick,
+ role = Role.Tab,
interactionState = interactionState,
indication = ripple
)
@@ -230,9 +233,9 @@
// TODO: should we animate the width of the indicator as it moves between tabs of different
// sizes inside a scrollable tab row?
val currentTabWidth = currentTabPosition.width
- val indicatorOffset = animate(
- target = currentTabPosition.left,
- animSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
+ val indicatorOffset by animateAsState(
+ targetValue = currentTabPosition.left,
+ animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
)
fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
@@ -322,9 +325,9 @@
// TODO: should we animate the width of the indicator as it moves between tabs of different
// sizes inside a scrollable tab row?
val currentTabWidth = currentTabPosition.width
- val indicatorOffset = animate(
- target = currentTabPosition.left,
- animSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
+ val indicatorOffset by animateAsState(
+ targetValue = currentTabPosition.left,
+ animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
)
fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
index 836a40d..dc4b069 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
@@ -93,6 +93,12 @@
* @param onValueChange the callback that is triggered when the input service updates the text. An
* updated text comes as a parameter of the callback
* @param modifier a [Modifier] for this text field
+ * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will
+ * be neither editable nor focusable, the input of the text field will not be selectable,
+ * visually text field will appear in the disabled UI state
+ * @param readOnly controls the editable state of the [TextField]. When `true`, the text fields
+ * will not be editable but otherwise operable. Read-only text fields are usually used to display
+ * the pre-filled text that user cannot edit
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [AmbientTextStyle] defined by the theme
* @param label the optional label to be displayed inside the text field container. The default
@@ -144,6 +150,8 @@
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ readOnly: Boolean = false,
textStyle: TextStyle = AmbientTextStyle.current,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
@@ -169,6 +177,8 @@
TextFieldImpl(
type = TextFieldType.Filled,
+ enabled = enabled,
+ readOnly = readOnly,
value = textFieldValue,
onValueChange = {
textFieldValueState = it
@@ -215,6 +225,12 @@
* @param onValueChange the callback that is triggered when the input service updates values in
* [TextFieldValue]. An updated [TextFieldValue] comes as a parameter of the callback
* @param modifier a [Modifier] for this text field
+ * @param enabled controls the enabled state of the [TextField]. When `false`, the text field will
+ * be neither editable nor focusable, the input of the text field will not be selectable,
+ * visually text field will appear in the disabled UI state
+ * @param readOnly controls the editable state of the [TextField]. When `true`, the text fields
+ * will not be editable but otherwise operable. Read-only text fields are usually used to display
+ * the pre-filled text that user cannot edit
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [AmbientTextStyle] defined by the theme
* @param label the optional label to be displayed inside the text field container. The default
@@ -266,6 +282,8 @@
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ readOnly: Boolean = false,
textStyle: TextStyle = AmbientTextStyle.current,
label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
@@ -288,6 +306,8 @@
) {
TextFieldImpl(
type = TextFieldType.Filled,
+ enabled = enabled,
+ readOnly = readOnly,
value = value,
onValueChange = onValueChange,
modifier = modifier,
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
index f378300..26707bd 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
@@ -75,6 +75,8 @@
@OptIn(ExperimentalFoundationApi::class)
internal fun TextFieldImpl(
type: TextFieldType,
+ enabled: Boolean,
+ readOnly: Boolean,
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier,
@@ -97,10 +99,11 @@
backgroundColor: Color,
shape: Shape
) {
+ // TODO(soboleva): b/171305338 provide colors object and apply alpha there instead
// If color is not provided via the text style, use content color as a default
val textColor = textStyle.color.takeOrElse {
- AmbientContentColor.current.copy(alpha = AmbientContentAlpha.current)
- }
+ AmbientContentColor.current
+ }.copy(alpha = if (enabled) AmbientContentAlpha.current else ContentAlpha.disabled)
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
val keyboardController: Ref<SoftwareKeyboardController> = remember { Ref() }
@@ -116,12 +119,14 @@
Decoration(
contentColor = inactiveColor,
typography = MaterialTheme.typography.subtitle1,
- contentAlpha = ContentAlpha.high
+ contentAlpha = if (enabled) ContentAlpha.high else ContentAlpha.disabled
) {
BasicTextField(
value = value,
modifier = tagModifier.defaultMinSizeConstraints(minWidth = TextFieldMinWidth),
textStyle = mergedTextStyle,
+ enabled = enabled,
+ readOnly = readOnly,
onValueChange = onValueChange,
cursorColor = if (isErrorValue) errorColor else activeColor,
visualTransformation = visualTransformation,
@@ -141,19 +146,23 @@
}
val focusReference = FocusReference()
- val textFieldModifier = modifier
- .focusReference(focusReference)
- .let {
- it.clickable(interactionState = interactionState, indication = null) {
+ val textFieldModifier = if (enabled) {
+ modifier
+ .focusReference(focusReference)
+ .clickable(interactionState = interactionState, indication = null) {
focusReference.requestFocus()
// TODO(b/163109449): Showing and hiding keyboard should be handled by BaseTextField.
// The requestFocus() call here should be enough to trigger the software keyboard.
// Investiate why this is needed here. If it is really needed, instead of doing
// this in the onClick callback, we should move this logic to onFocusChanged
// so that it can show or hide the keyboard based on the focus state.
- keyboardController.value?.showSoftwareKeyboard()
+ if (!readOnly) {
+ keyboardController.value?.showSoftwareKeyboard()
+ }
}
- }
+ } else {
+ modifier
+ }
TextFieldTransitionScope.Transition(
inputState = inputState,
@@ -166,11 +175,13 @@
labelInactiveColor = if (isErrorValue) {
errorColor
} else {
- inactiveColor.applyAlpha(alpha = ContentAlpha.medium)
+ inactiveColor.applyAlpha(if (enabled) ContentAlpha.medium else ContentAlpha.disabled)
},
indicatorInactiveColor = when {
isErrorValue -> errorColor
- type == TextFieldType.Filled -> inactiveColor.applyAlpha(alpha = IndicatorInactiveAlpha)
+ type == TextFieldType.Filled -> inactiveColor.applyAlpha(
+ if (enabled) IndicatorInactiveAlpha else ContentAlpha.disabled
+ )
else -> inactiveColor.applyAlpha(alpha = ContentAlpha.disabled)
}
@@ -202,7 +213,8 @@
Decoration(
contentColor = inactiveColor,
typography = MaterialTheme.typography.subtitle1,
- contentAlpha = ContentAlpha.medium,
+ contentAlpha =
+ if (enabled) ContentAlpha.medium else ContentAlpha.disabled,
content = placeholder
)
}
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt
index 6928095..4a51a41 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt
@@ -123,6 +123,7 @@
" .-Node #X at \\(X, X, X, X\\)px, Tag: 'box'\n" +
" . Disabled = 'kotlin.Unit'\n" +
" . .-Node #X at \\(X, X, X, X\\)px\n" +
+ " . Role = 'Button'\n" +
" . OnClick = 'AccessibilityAction\\(label=null, action=.*\\)'\n" +
" . Text = 'Button'\n" +
" . GetTextLayoutResult = 'AccessibilityAction\\(label=null, " +
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index b64837d..7415a2e 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2639,6 +2639,14 @@
property public final CharSequence label;
}
+ public enum Role {
+ enum_constant public static final androidx.compose.ui.semantics.Role Button;
+ enum_constant public static final androidx.compose.ui.semantics.Role Checkbox;
+ enum_constant public static final androidx.compose.ui.semantics.Role RadioButton;
+ enum_constant public static final androidx.compose.ui.semantics.Role Switch;
+ enum_constant public static final androidx.compose.ui.semantics.Role Tab;
+ }
+
public final class SemanticsActions {
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getCopyText();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction>> getCustomActions();
@@ -2756,6 +2764,7 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.input.ImeAction> getImeAction();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsDialog();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsPopup();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.Role> getRole();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getSelected();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getStateDescription();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
@@ -2772,6 +2781,7 @@
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.input.ImeAction> ImeAction;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsDialog;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsPopup;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.Role> Role;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Selected;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> StateDescription;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
@@ -2795,6 +2805,7 @@
method public static boolean getFocused(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.semantics.AccessibilityScrollState getHorizontalAccessibilityScrollState(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.text.input.ImeAction getImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.compose.ui.semantics.Role getRole(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static boolean getSelected(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getStateDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.semantics.AccessibilityRangeInfo getStateDescriptionRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -2818,6 +2829,7 @@
method public static void setHorizontalAccessibilityScrollState(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.AccessibilityScrollState p);
method public static void setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.input.ImeAction p);
method public static void setProgress(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> action);
+ method public static void setRole(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.Role p);
method public static void setSelected(androidx.compose.ui.semantics.SemanticsPropertyReceiver, boolean p);
method public static void setSelection(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Boolean,java.lang.Boolean> action);
method public static void setStateDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String p);
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index b64837d..7415a2e 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -2639,6 +2639,14 @@
property public final CharSequence label;
}
+ public enum Role {
+ enum_constant public static final androidx.compose.ui.semantics.Role Button;
+ enum_constant public static final androidx.compose.ui.semantics.Role Checkbox;
+ enum_constant public static final androidx.compose.ui.semantics.Role RadioButton;
+ enum_constant public static final androidx.compose.ui.semantics.Role Switch;
+ enum_constant public static final androidx.compose.ui.semantics.Role Tab;
+ }
+
public final class SemanticsActions {
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getCopyText();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction>> getCustomActions();
@@ -2756,6 +2764,7 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.input.ImeAction> getImeAction();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsDialog();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsPopup();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.Role> getRole();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getSelected();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getStateDescription();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
@@ -2772,6 +2781,7 @@
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.input.ImeAction> ImeAction;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsDialog;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsPopup;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.Role> Role;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Selected;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> StateDescription;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
@@ -2795,6 +2805,7 @@
method public static boolean getFocused(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.semantics.AccessibilityScrollState getHorizontalAccessibilityScrollState(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.text.input.ImeAction getImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.compose.ui.semantics.Role getRole(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static boolean getSelected(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getStateDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.semantics.AccessibilityRangeInfo getStateDescriptionRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -2818,6 +2829,7 @@
method public static void setHorizontalAccessibilityScrollState(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.AccessibilityScrollState p);
method public static void setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.input.ImeAction p);
method public static void setProgress(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> action);
+ method public static void setRole(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.Role p);
method public static void setSelected(androidx.compose.ui.semantics.SemanticsPropertyReceiver, boolean p);
method public static void setSelection(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Boolean,java.lang.Boolean> action);
method public static void setStateDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String p);
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 88c1c65..05d3e9b 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2701,6 +2701,14 @@
property public final CharSequence label;
}
+ public enum Role {
+ enum_constant public static final androidx.compose.ui.semantics.Role Button;
+ enum_constant public static final androidx.compose.ui.semantics.Role Checkbox;
+ enum_constant public static final androidx.compose.ui.semantics.Role RadioButton;
+ enum_constant public static final androidx.compose.ui.semantics.Role Switch;
+ enum_constant public static final androidx.compose.ui.semantics.Role Tab;
+ }
+
public final class SemanticsActions {
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getCopyText();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction>> getCustomActions();
@@ -2818,6 +2826,7 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.input.ImeAction> getImeAction();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsDialog();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsPopup();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.Role> getRole();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getSelected();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getStateDescription();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
@@ -2834,6 +2843,7 @@
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.input.ImeAction> ImeAction;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsDialog;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsPopup;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.Role> Role;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> Selected;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> StateDescription;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
@@ -2857,6 +2867,7 @@
method public static boolean getFocused(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.semantics.AccessibilityScrollState getHorizontalAccessibilityScrollState(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.text.input.ImeAction getImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.compose.ui.semantics.Role getRole(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static boolean getSelected(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getStateDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.semantics.AccessibilityRangeInfo getStateDescriptionRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -2880,6 +2891,7 @@
method public static void setHorizontalAccessibilityScrollState(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.AccessibilityScrollState p);
method public static void setImeAction(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.input.ImeAction p);
method public static void setProgress(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> action);
+ method public static void setRole(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.Role p);
method public static void setSelected(androidx.compose.ui.semantics.SemanticsPropertyReceiver, boolean p);
method public static void setSelection(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Boolean,java.lang.Boolean> action);
method public static void setStateDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String p);
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ReuseFocusReference.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ReuseFocusReference.kt
index c091c1c..e460a2b 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ReuseFocusReference.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ReuseFocusReference.kt
@@ -16,8 +16,8 @@
package androidx.compose.ui.demos.focus
-import androidx.compose.animation.animate
import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.animateAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -76,7 +76,7 @@
@Composable
private fun Circle(modifier: Modifier = Modifier, nextShape: () -> Unit) {
var isFocused by remember { mutableStateOf(false) }
- val scale = animate(if (isFocused) 0f else 1f, TweenSpec(2000)) {
+ val scale by animateAsState(if (isFocused) 0f else 1f, TweenSpec(2000)) {
if (it == 0f) {
nextShape()
}
@@ -99,7 +99,7 @@
@Composable
private fun Square(modifier: Modifier = Modifier, nextShape: () -> Unit) {
var isFocused by remember { mutableStateOf(false) }
- val scale = animate(if (isFocused) 0f else 1f, TweenSpec(2000)) {
+ val scale by animateAsState(if (isFocused) 0f else 1f, TweenSpec(2000)) {
if (it == 0f) {
nextShape()
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
index 27ac730..f63d998 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
@@ -33,6 +33,7 @@
import androidx.compose.ui.semantics.SemanticsModifierCore
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.SemanticsWrapper
import androidx.compose.ui.semantics.copyText
import androidx.compose.ui.semantics.cutText
@@ -46,6 +47,7 @@
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.onLongClick
import androidx.compose.ui.semantics.pasteText
+import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.setProgress
import androidx.compose.ui.semantics.setSelection
import androidx.compose.ui.semantics.setText
@@ -66,6 +68,7 @@
import com.nhaarman.mockitokotlin2.spy
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
+import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -85,6 +88,7 @@
private lateinit var accessibilityDelegate: AndroidComposeViewAccessibilityDelegateCompat
private lateinit var container: ViewGroup
private lateinit var androidComposeView: AndroidComposeView
+ private lateinit var info: AccessibilityNodeInfoCompat
@Before
fun setup() {
@@ -109,23 +113,24 @@
rule.setContent {
AmbientClipboardManager.current.setText(AnnotatedString("test"))
}
+ info = AccessibilityNodeInfoCompat.obtain()
+ }
+
+ @After
+ fun cleanup() {
+ info.recycle()
}
@Test
fun testPopulateAccessibilityNodeInfoProperties_general() {
- val info = AccessibilityNodeInfoCompat.obtain()
val clickActionLabel = "click"
val dismissActionLabel = "dismiss"
val stateDescription = "checked"
- val semanticsModifier = SemanticsModifierCore(1, true, false) {
+ val semanticsNode = createSemanticsNodeWithProperties(1, true) {
this.stateDescription = stateDescription
onClick(clickActionLabel) { true }
dismiss(dismissActionLabel) { true }
}
- val semanticsNode = SemanticsNode(
- SemanticsWrapper(InnerPlaceable(LayoutNode()), semanticsModifier),
- true
- )
accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
assertEquals("android.view.View", info.className)
assertTrue(
@@ -166,7 +171,6 @@
@Test
fun testPopulateAccessibilityNodeInfoProperties_disabled() {
- val info = AccessibilityNodeInfoCompat.obtain()
val semanticsNode = createSemanticsNodeWithProperties(1, true) {
disabled()
text = AnnotatedString("text")
@@ -244,21 +248,56 @@
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_RIGHT
)
)
+ }
+
+ @Test
+ fun testPopulateAccessibilityNodeInfoProperties_role() {
+ var semanticsNode = createSemanticsNodeWithProperties(1, true) {
+ role = Role.Button
+ }
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
+ assertEquals("android.widget.Button", info.className)
info.recycle()
+
+ info = AccessibilityNodeInfoCompat.obtain()
+ semanticsNode = createSemanticsNodeWithProperties(1, true) {
+ role = Role.Switch
+ }
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
+ assertEquals("android.widget.Switch", info.className)
+ info.recycle()
+
+ info = AccessibilityNodeInfoCompat.obtain()
+ semanticsNode = createSemanticsNodeWithProperties(1, true) {
+ role = Role.Checkbox
+ }
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
+ assertEquals("android.widget.CheckBox", info.className)
+ info.recycle()
+
+ info = AccessibilityNodeInfoCompat.obtain()
+ semanticsNode = createSemanticsNodeWithProperties(1, true) {
+ role = Role.RadioButton
+ }
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
+ assertEquals("android.widget.RadioButton", info.className)
+ info.recycle()
+
+ info = AccessibilityNodeInfoCompat.obtain()
+ semanticsNode = createSemanticsNodeWithProperties(1, true) {
+ role = Role.Tab
+ }
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
+ assertEquals("Tab", info.roleDescription)
}
@Test
fun testPopulateAccessibilityNodeInfoProperties_SeekBar() {
- val info = AccessibilityNodeInfoCompat.obtain()
val setProgressActionLabel = "setProgress"
- val semanticsModifier = SemanticsModifierCore(1, true, false) {
+ val semanticsNode = createSemanticsNodeWithProperties(1, true) {
stateDescriptionRange = AccessibilityRangeInfo(0.5f, 0f..1f, 6)
setProgress(setProgressActionLabel) { true }
}
- val semanticsNode = SemanticsNode(
- SemanticsWrapper(InnerPlaceable(LayoutNode()), semanticsModifier),
- true
- )
accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
assertEquals("android.widget.SeekBar", info.className)
assertEquals(
@@ -283,11 +322,10 @@
@Test
fun testPopulateAccessibilityNodeInfoProperties_EditText() {
- val info = AccessibilityNodeInfoCompat.obtain()
val setSelectionActionLabel = "setSelection"
val setTextActionLabel = "setText"
val text = "hello"
- val semanticsModifier = SemanticsModifierCore(1, true, false) {
+ val semanticsNode = createSemanticsNodeWithProperties(1, true) {
this.text = AnnotatedString(text)
this.textSelectionRange = TextRange(1)
this.focused = true
@@ -295,10 +333,6 @@
setText(setTextActionLabel) { true }
setSelection(setSelectionActionLabel) { _, _, _ -> true }
}
- val semanticsNode = SemanticsNode(
- SemanticsWrapper(InnerPlaceable(LayoutNode()), semanticsModifier),
- true
- )
accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, semanticsNode)
assertEquals("android.widget.EditText", info.className)
assertEquals(SpannableString(text), info.text)
@@ -355,7 +389,6 @@
@Test
fun test_PasteAction_ifFocused() {
- val info = AccessibilityNodeInfoCompat.obtain()
val semanticsNode = createSemanticsNodeWithProperties(1, true) {
focused = true
pasteText {
@@ -378,7 +411,6 @@
@Test
fun test_noPasteAction_ifUnfocused() {
- val info = AccessibilityNodeInfoCompat.obtain()
val semanticsNode = createSemanticsNodeWithProperties(1, true) {
pasteText {
true
@@ -427,13 +459,13 @@
val oldSemanticsNode = createSemanticsNodeWithProperties(2, false) {
this.verticalAccessibilityScrollState = AccessibilityScrollState(0f, 5f, false)
}
- accessibilityDelegate.semanticsNodes[1] =
+ accessibilityDelegate.semanticsNodes[2] =
AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(oldSemanticsNode)
val newSemanticsNode = createSemanticsNodeWithProperties(2, false) {
this.verticalAccessibilityScrollState = AccessibilityScrollState(2f, 5f, false)
}
val newNodes = mutableMapOf<Int, SemanticsNode>()
- newNodes[1] = newSemanticsNode
+ newNodes[2] = newSemanticsNode
accessibilityDelegate.sendSemanticsPropertyChangeEvents(newNodes)
verify(container, times(1)).requestSendAccessibilityEvent(
@@ -452,6 +484,77 @@
)
}
+ @Test
+ fun sendWindowContentChangeUndefinedEventByDefault_whenPropertyAdded() {
+ val oldSemanticsNode = createSemanticsNodeWithProperties(1, false) {}
+ accessibilityDelegate.semanticsNodes[1] =
+ AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(oldSemanticsNode)
+ val newSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+ disabled()
+ }
+ val newNodes = mutableMapOf<Int, SemanticsNode>()
+ newNodes[1] = newSemanticsNode
+ accessibilityDelegate.sendSemanticsPropertyChangeEvents(newNodes)
+
+ verify(container, times(1)).requestSendAccessibilityEvent(
+ eq(androidComposeView),
+ argThat(
+ ArgumentMatcher {
+ it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
+ it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
+ }
+ )
+ )
+ }
+
+ @Test
+ fun sendWindowContentChangeUndefinedEventByDefault_whenPropertyRemoved() {
+ val oldSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+ disabled()
+ }
+ accessibilityDelegate.semanticsNodes[1] =
+ AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(oldSemanticsNode)
+ val newSemanticsNode = createSemanticsNodeWithProperties(1, false) {}
+ val newNodes = mutableMapOf<Int, SemanticsNode>()
+ newNodes[1] = newSemanticsNode
+ accessibilityDelegate.sendSemanticsPropertyChangeEvents(newNodes)
+
+ verify(container, times(1)).requestSendAccessibilityEvent(
+ eq(androidComposeView),
+ argThat(
+ ArgumentMatcher {
+ it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
+ it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
+ }
+ )
+ )
+ }
+
+ @Test
+ fun sendWindowContentChangeUndefinedEventByDefault_onlyOnce_whenMultiplePropertiesChange() {
+ val oldSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+ disabled()
+ }
+ accessibilityDelegate.semanticsNodes[1] =
+ AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(oldSemanticsNode)
+ val newSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+ onClick { true }
+ }
+ val newNodes = mutableMapOf<Int, SemanticsNode>()
+ newNodes[1] = newSemanticsNode
+ accessibilityDelegate.sendSemanticsPropertyChangeEvents(newNodes)
+
+ verify(container, times(1)).requestSendAccessibilityEvent(
+ eq(androidComposeView),
+ argThat(
+ ArgumentMatcher {
+ it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
+ it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
+ }
+ )
+ )
+ }
+
private fun createSemanticsNodeWithProperties(
id: Int,
mergeDescendants: Boolean,
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index cd7dad9..8060931 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -29,7 +29,7 @@
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.annotation.RequiresApi
-import androidx.compose.animation.animate
+import androidx.compose.animation.core.animateAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
@@ -3004,7 +3004,7 @@
activity.setContent {
Box(Modifier.background(Color.Red).drawLatchModifier()) {
var animatedSize by remember { mutableStateOf(size) }
- animatedSize = animate(size)
+ animatedSize = animateAsState(size).value
if (animatedSize == 10f) {
Layout(
modifier = Modifier.background(Color.Cyan),
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AccessibilityRoleDescriptions.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AccessibilityRoleDescriptions.kt
new file mode 100644
index 0000000..520c4ec
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AccessibilityRoleDescriptions.kt
@@ -0,0 +1,23 @@
+/*
+ * 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
+
+// TODO(b/138327849): (STOPSHIP) Move all of these to resources once we have a real resources system,
+// then delete this class
+internal object AccessibilityRoleDescriptions {
+ const val Tab = "Tab"
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.kt
index 4846d92..50ec9a3 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.kt
@@ -41,6 +41,7 @@
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.node.LayoutNode
import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsActions.CustomActions
import androidx.compose.ui.semantics.SemanticsNode
@@ -58,6 +59,7 @@
import androidx.compose.ui.util.fastForEach
import androidx.core.view.AccessibilityDelegateCompat
import androidx.core.view.ViewCompat
+import androidx.core.view.accessibility.AccessibilityEventCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.core.view.accessibility.AccessibilityNodeProviderCompat
import kotlinx.coroutines.channels.Channel
@@ -224,8 +226,16 @@
info: AccessibilityNodeInfoCompat,
semanticsNode: SemanticsNode
) {
- // TODO(b/151240295): Should we have widgets class name?
info.className = ClassName
+ semanticsNode.config.getOrNull(SemanticsProperties.Role)?.let {
+ when (it) {
+ Role.Button -> info.className = "android.widget.Button"
+ Role.Checkbox -> info.className = "android.widget.CheckBox"
+ Role.Switch -> info.className = "android.widget.Switch"
+ Role.RadioButton -> info.className = "android.widget.RadioButton"
+ Role.Tab -> info.roleDescription = AccessibilityRoleDescriptions.Tab
+ }
+ }
info.packageName = view.context.packageName
try {
info.setBoundsInScreen(
@@ -1300,16 +1310,13 @@
@VisibleForTesting
internal fun sendSemanticsPropertyChangeEvents(newSemanticsNodes: Map<Int, SemanticsNode>) {
for (id in newSemanticsNodes.keys) {
- if (!semanticsNodes.contains(id)) {
- continue
- }
-
// We do doing this search because the new configuration is set as a whole, so we
// can't indicate which property is changed when setting the new configuration.
+ val oldNode = semanticsNodes[id] ?: continue
val newNode = newSemanticsNodes[id]
- val oldNode = semanticsNodes[id]
+ var propertyChanged = false
for (entry in newNode!!.config) {
- if (entry.value == oldNode!!.config.getOrNull(entry.key)) {
+ if (entry.value == oldNode.config.getOrNull(entry.key)) {
continue
}
when (entry.key) {
@@ -1317,7 +1324,7 @@
sendEventForVirtualView(
semanticsNodeIdToAccessibilityVirtualNodeId(id),
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
- AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION
+ AccessibilityEventCompat.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION
)
SemanticsProperties.ContentDescription ->
sendEventForVirtualView(
@@ -1463,11 +1470,26 @@
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
)
}
+ // TODO(b/151840490) send the correct events for certain properties, like pane
+ // title, view selected.
else -> {
- // TODO(b/151840490) send the correct events when property changes
+ propertyChanged = true
}
}
}
+
+ if (!propertyChanged) {
+ propertyChanged = newNode.propertiesDeleted(oldNode)
+ }
+ if (propertyChanged) {
+ // TODO(b/176105563): throttle the window content change events and merge different
+ // sub types. We can use the subtreeChangedLayoutNodes with sub types.
+ sendEventForVirtualView(
+ semanticsNodeIdToAccessibilityVirtualNodeId(id),
+ AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
+ )
+ }
}
}
@@ -1764,3 +1786,14 @@
}
private fun SemanticsNode.enabled() = (config.getOrNull(SemanticsProperties.Disabled) == null)
+
+private fun SemanticsNode.propertiesDeleted(
+ oldNode: AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy
+): Boolean {
+ for (entry in oldNode.config) {
+ if (!config.contains(entry.key)) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
index bc823ec..8cbeb74 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
@@ -130,6 +130,17 @@
}
)
+ /**
+ * The type of user interface element. Accessibility services might use this to describe the
+ * element or do customizations. Most roles can be automatically resolved by the semantics
+ * properties of this element. But some elements with subtle differences need an exact role. If
+ * an exact role is not listed in [Role], this property should not be set and the framework will
+ * automatically resolve it.
+ *
+ * @see SemanticsPropertyReceiver.role
+ */
+ val Role = SemanticsPropertyKey<Role>("Role")
+
// TODO(b/138172781): Move to FoundationSemanticsProperties
/**
* Test tag attached to this semantics node.
@@ -403,6 +414,48 @@
val reverseScrolling: Boolean = false
)
+/**
+ * The type of user interface element. Accessibility services might use this to describe the
+ * element or do customizations. Most roles can be automatically resolved by the semantics
+ * properties of this element. But some elements with subtle differences need an exact role. If an
+ * exact role is not listed, [SemanticsPropertyReceiver.role] should not be set and the framework
+ * will automatically resolve it.
+ */
+enum class Role {
+ /**
+ * This element is a button control. Associated semantics properties for accessibility:
+ * [SemanticsProperties.Disabled], [SemanticsActions.OnClick]
+ */
+ Button,
+ /**
+ * This element is a Checkbox which is a component that represents two states (checked /
+ * unchecked). Associated semantics properties for accessibility:
+ * [SemanticsProperties.Disabled], [SemanticsProperties.StateDescription],
+ * [SemanticsActions.OnClick]
+ */
+ Checkbox,
+ /**
+ * This element is a Switch which is a two state toggleable component that provides on/off
+ * like options. Associated semantics properties for accessibility:
+ * [SemanticsProperties.Disabled], [SemanticsProperties.StateDescription],
+ * [SemanticsActions.OnClick]
+ */
+ Switch,
+ /**
+ * This element is a RadioButton which is a component to represent two states, selected and not
+ * selected. Associated semantics properties for accessibility: [SemanticsProperties.Disabled],
+ * [SemanticsProperties.StateDescription], [SemanticsActions.OnClick]
+ */
+ RadioButton,
+ /**
+ * This element is a Tab which represents a single page of content using a text label and/or
+ * icon. A Tab also has two states: selected and not selected. Associated semantics properties
+ * for accessibility: [SemanticsProperties.Disabled], [SemanticsProperties.StateDescription],
+ * [SemanticsActions.OnClick]
+ */
+ Tab
+}
+
interface SemanticsPropertyReceiver {
operator fun <T> set(key: SemanticsPropertyKey<T>, value: T)
}
@@ -503,6 +556,17 @@
this[SemanticsProperties.IsDialog] = Unit
}
+/**
+ * The type of user interface element. Accessibility services might use this to describe the
+ * element or do customizations. Most roles can be automatically resolved by the semantics
+ * properties of this element. But some elements with subtle differences need an exact role. If
+ * an exact role is not listed in [Role], this property should not be set and the framework will
+ * automatically resolve it.
+ *
+ * @see SemanticsProperties.Role
+ */
+var SemanticsPropertyReceiver.role by SemanticsProperties.Role
+
// TODO(b/138172781): Move to FoundationSemanticsProperties.kt
/**
* Test tag attached to this semantics node.
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
index 7f0aca2..1c5ef6e 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
@@ -16,9 +16,9 @@
package androidx.compose.ui.platform
-import androidx.compose.animation.animate
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.animateAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -213,9 +213,9 @@
var targetValue by mutableStateOf(10f)
setContent {
- val value = animate(
+ val value by animateAsState(
targetValue,
- animSpec = TweenSpec(durationMillis = 30, easing = LinearEasing)
+ animationSpec = TweenSpec(durationMillis = 30, easing = LinearEasing)
)
Box(Modifier.size(value.dp).background(Color.Blue))
}
diff --git a/development/diagnose-build-failure/README.md b/development/diagnose-build-failure/README.md
index 106a7d1..66661ca 100644
--- a/development/diagnose-build-failure/README.md
+++ b/development/diagnose-build-failure/README.md
@@ -1,3 +1,5 @@
-For automated help diagnosing build failures, run diagnose-build-failure.sh
+* For automated help diagnosing build failures, run diagnose-build-failure.sh
-In the future we might add additional, hard-to-automate suggestions into this README.md too
+ * To see a list of the possible diagnoses it can make, run it with no arguments
+
+* In the future, this file might contain additional, hard-to-automate suggestions too
diff --git a/development/diagnose-build-failure/diagnose-build-failure.sh b/development/diagnose-build-failure/diagnose-build-failure.sh
index 9d9dcae..892e033 100755
--- a/development/diagnose-build-failure/diagnose-build-failure.sh
+++ b/development/diagnose-build-failure/diagnose-build-failure.sh
@@ -26,6 +26,7 @@
echo
echo " A) Some state saved in memory by the Gradle daemon is triggering an error"
echo " B) Your source files have been changed"
+ echo " To (slowly) generate a simpler reproduction case, you can run simplify-build-failure.sh"
echo " C) Some file in the out/ dir is triggering an error"
echo " If this happens, $scriptName will identify which file(s) specifically"
echo " D) The build is nondeterministic and/or affected by timestamps"
diff --git a/gradlew b/gradlew
index 00d0aeb..ded855b 100755
--- a/gradlew
+++ b/gradlew
@@ -253,7 +253,8 @@
# Have to do this build-failure detection in gradlew rather than in build.gradle
# so that this message still prints even if buildSrc itself fails
echo
- echo See also development/diagnose-build-failure for help with build failures in this project.
+ echo For help with unexpected failures, see development/diagnose-build-failure/README.md
+ echo
return 1
fi
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
index 06d6d7a..754c494 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcher.kt
@@ -21,7 +21,6 @@
import androidx.paging.LoadType.REFRESH
import androidx.paging.RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collect
@@ -31,7 +30,6 @@
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.launch
-@OptIn(FlowPreview::class)
internal class PageFetcher<Key : Any, Value : Any>(
private val pagingSourceFactory: suspend () -> PagingSource<Key, Value>,
private val initialKey: Key?,
@@ -94,7 +92,7 @@
previousGeneration?.snapshot?.close()
GenerationInfo(
- snapshot = PageFetcherSnapshot<Key, Value>(
+ snapshot = PageFetcherSnapshot(
initialKey = initialKey,
pagingSource = pagingSource,
config = config,
@@ -112,7 +110,6 @@
.mapLatest { generation ->
val downstreamFlow = generation.snapshot
.injectRemoteEvents(remoteMediatorAccessor)
- // .mapRemoteCompleteAsTrailingInsertForSeparators()
PagingData(
flow = downstreamFlow,
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
index 020ae17..8e11fb9 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
@@ -30,15 +30,12 @@
import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
-import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
-import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
import kotlinx.coroutines.channels.ClosedSendChannelException
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.conflate
@@ -73,8 +70,7 @@
}
}
- @OptIn(ExperimentalCoroutinesApi::class)
- private val hintChannel = BroadcastChannel<ViewportHint>(CONFLATED)
+ private val hintSharedFlow = MutableSharedFlow<ViewportHint>(replay = 1)
private var lastHint: ViewportHint.Access? = null
private val pageEventChCollected = AtomicBoolean(false)
@@ -83,7 +79,6 @@
private val pageEventChannelFlowJob = Job()
- @OptIn(ExperimentalCoroutinesApi::class)
val pageEventFlow: Flow<PageEvent<Value>> = cancelableChannelFlow(pageEventChannelFlowJob) {
check(pageEventChCollected.compareAndSet(false, true)) {
"Attempt to collect twice from pageEventFlow, which is an illegal operation. Did you " +
@@ -97,6 +92,7 @@
// Protect against races where a subsequent call to submitData invoked close(),
// but a pageEvent arrives after closing causing ClosedSendChannelException.
try {
+ @OptIn(ExperimentalCoroutinesApi::class)
send(it)
} catch (e: ClosedSendChannelException) {
// Safe to drop PageEvent here, since collection has been cancelled.
@@ -184,8 +180,7 @@
"Cannot retry APPEND / PREPEND load on PagingSource without ViewportHint"
}
- @OptIn(ExperimentalCoroutinesApi::class)
- hintChannel.offer(viewportHint)
+ hintSharedFlow.tryEmit(viewportHint)
}
}
}
@@ -195,8 +190,7 @@
lastHint = viewportHint
}
- @OptIn(ExperimentalCoroutinesApi::class)
- hintChannel.offer(viewportHint)
+ hintSharedFlow.tryEmit(viewportHint)
}
fun close() {
@@ -216,12 +210,11 @@
}
}
- @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
private fun CoroutineScope.startConsumingHints() {
// Pseudo-tiling via invalidation on jumps.
if (config.jumpThreshold != COUNT_UNDEFINED) {
launch {
- hintChannel.asFlow()
+ hintSharedFlow
.filter { hint ->
hint.presentedItemsBefore * -1 > config.jumpThreshold ||
hint.presentedItemsAfter * -1 > config.jumpThreshold
@@ -243,7 +236,7 @@
/**
* Maps a [Flow] of generation ids from [PageFetcherSnapshotState] to [ViewportHint]s from
- * [hintChannel] with back-pressure handling via conflation by prioritizing hints which
+ * [hintSharedFlow] with back-pressure handling via conflation by prioritizing hints which
* either update presenter state or those that would load the most items.
*
* @param loadType [PREPEND] or [APPEND]
@@ -265,8 +258,7 @@
}
}
- @OptIn(FlowPreview::class)
- hintChannel.asFlow()
+ hintSharedFlow
// Prevent infinite loop when competing PREPEND / APPEND cancel each other
.drop(if (generationId == 0) 0 else 1)
.map { hint -> GenerationalViewportHint(generationId, hint) }
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
index 3f93b05..9785d94 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -24,7 +24,6 @@
import androidx.paging.PagePresenter.ProcessPageEventCallback
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
@@ -305,7 +304,6 @@
*
* @sample androidx.paging.samples.loadStateFlowSample
*/
- @OptIn(FlowPreview::class)
val loadStateFlow: Flow<CombinedLoadStates>
get() = _combinedLoadState
diff --git a/paging/common/src/main/kotlin/androidx/paging/RemoteMediatorAccessor.kt b/paging/common/src/main/kotlin/androidx/paging/RemoteMediatorAccessor.kt
index a2a7aa1..addb540 100644
--- a/paging/common/src/main/kotlin/androidx/paging/RemoteMediatorAccessor.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/RemoteMediatorAccessor.kt
@@ -44,6 +44,7 @@
internal interface RemoteMediatorAccessor<Key : Any, Value : Any> :
RemoteMediatorConnection<Key, Value> {
val state: StateFlow<LoadStates>
+
suspend fun initialize(): RemoteMediator.InitializeAction
}
@@ -138,7 +139,11 @@
return false
}
val blockState = blockStates[loadType.ordinal]
- if (blockState != UNBLOCKED) {
+ // Ignore block state for REFRESH as it is only sent in cases where we want to clear all
+ // AccessorState, but we cannot simply generate a new one for an existing PageFetcher as
+ // we need to cancel in-flight requests and prevent races between clearing state and
+ // triggering remote REFRESH by clearing state as part of handling the load request.
+ if (blockState != UNBLOCKED && loadType != LoadType.REFRESH) {
return false
}
if (loadType == LoadType.REFRESH) {
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
index 232c4f7..559702b 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
@@ -35,7 +35,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
@@ -67,7 +66,7 @@
import kotlin.test.assertTrue
import kotlin.test.fail
-@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(JUnit4::class)
class PageFetcherSnapshotTest {
private val testScope = TestCoroutineScope()
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
index 4cb76d0..6569c70 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
@@ -89,6 +89,61 @@
}
@Test
+ fun refresh_sourceEndOfPaginationReached() = testScope.runBlockingTest {
+ @OptIn(ExperimentalPagingApi::class)
+ val pageFetcher = PageFetcher(
+ pagingSourceFactory = { TestPagingSource(items = emptyList()) },
+ initialKey = 0,
+ config = config,
+ )
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertEquals(1, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
+
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertEquals(2, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
+ fetcherState.job.cancel()
+ }
+
+ @Test
+ fun refresh_remoteEndOfPaginationReached() = testScope.runBlockingTest {
+ @OptIn(ExperimentalPagingApi::class)
+ val remoteMediator = RemoteMediatorMock().apply {
+ initializeResult = LAUNCH_INITIAL_REFRESH
+ loadCallback = { _, _ ->
+ RemoteMediator.MediatorResult.Success(endOfPaginationReached = true)
+ }
+ }
+ val pageFetcher = PageFetcher(
+ pagingSourceFactory = { TestPagingSource(items = emptyList()) },
+ initialKey = 0,
+ config = config,
+ remoteMediator = remoteMediator
+ )
+ val fetcherState = collectFetcherState(pageFetcher)
+
+ advanceUntilIdle()
+
+ assertEquals(1, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[0].isNotEmpty() }
+ assertEquals(1, remoteMediator.loadEvents.size)
+
+ pageFetcher.refresh()
+ advanceUntilIdle()
+
+ assertEquals(2, fetcherState.pagingDataList.size)
+ assertTrue { fetcherState.pageEventLists[1].isNotEmpty() }
+ assertEquals(2, remoteMediator.loadEvents.size)
+ fetcherState.job.cancel()
+ }
+
+ @Test
fun refresh_fromPagingSource() = testScope.runBlockingTest {
var pagingSource: PagingSource<Int, Int>? = null
val pagingSourceFactory = suspend {
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt b/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
index 8cb757b..6b08423 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
@@ -24,7 +24,6 @@
import androidx.recyclerview.widget.ListUpdateCallback
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -249,7 +248,6 @@
*
* @sample androidx.paging.samples.loadStateFlowSample
*/
- @OptIn(FlowPreview::class)
val loadStateFlow: Flow<CombinedLoadStates> = differBase.loadStateFlow
/**
diff --git a/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt b/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
index 82892f3..ba0c9ed 100644
--- a/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
+++ b/paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt
@@ -25,7 +25,6 @@
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
/**
@@ -240,7 +239,6 @@
* This flow is conflated, so it buffers the last update to [CombinedLoadStates] and
* immediately delivers the current load states on collection.
*/
- @OptIn(FlowPreview::class)
val loadStateFlow: Flow<CombinedLoadStates> = differ.loadStateFlow
/**
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
index 8cff604..4f284c2 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
@@ -24,6 +24,7 @@
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.isVoid
+import androidx.room.processor.ProcessorErrors.INNER_CLASS_TYPE_CONVERTER_MUST_BE_STATIC
import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_BAD_RETURN_TYPE
import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
@@ -81,7 +82,6 @@
}
fun process(): List<CustomTypeConverter> {
- // using element utils instead of MoreElements to include statics.
val methods = element.getAllMethods()
val converterMethods = methods.filter {
it.hasAnnotation(TypeConverter::class)
@@ -93,6 +93,11 @@
val isKotlinObjectDeclaration = element.isKotlinObject()
if (!isProvidedConverter) {
context.checker.check(
+ element.enclosingTypeElement == null || element.isStatic(),
+ element,
+ INNER_CLASS_TYPE_CONVERTER_MUST_BE_STATIC
+ )
+ context.checker.check(
isKotlinObjectDeclaration || allStatic || constructors.isEmpty() ||
constructors.any {
it.parameters.isEmpty()
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index a10bbb0..0132761 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -260,7 +260,6 @@
the mapping.
You can annotate the method with @RewriteQueriesToDropUnusedColumns to direct Room
to rewrite your query to avoid fetching unused columns.
- ""${'"'}.trimIndent()
""".trim()
} else {
""
@@ -295,6 +294,8 @@
" have no-argument public constructors. Use a ProvidedTypeConverter annotation if you" +
" need to take control over creating an instance of a TypeConverter."
val TYPE_CONVERTER_MUST_BE_PUBLIC = "Type converters must be public."
+ val INNER_CLASS_TYPE_CONVERTER_MUST_BE_STATIC = "An inner class TypeConverter must be " +
+ "static."
fun duplicateTypeConverters(converters: List<CustomTypeConverter>): String {
return "Multiple methods define the same conversion. Conflicts with these:" +
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/ByteBufferColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/ByteBufferColumnTypeAdapter.kt
index 981a606..c7b8a8e 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/types/ByteBufferColumnTypeAdapter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/ByteBufferColumnTypeAdapter.kt
@@ -35,11 +35,18 @@
indexVarName: String,
scope: CodeGenScope
) {
- scope.builder()
- .addStatement(
- "$L = $T.wrap($L.getBlob($L))",
- outVarName, TypeName.get(ByteBuffer::class.java), cursorVarName, indexVarName
- )
+ scope.builder().apply {
+ beginControlFlow("if ($L.isNull($L))", cursorVarName, indexVarName).apply {
+ addStatement("$L = null", outVarName)
+ }
+ nextControlFlow("else").apply {
+ addStatement(
+ "$L = $T.wrap($L.getBlob($L))",
+ outVarName, TypeName.get(ByteBuffer::class.java), cursorVarName, indexVarName
+ )
+ }
+ endControlFlow()
+ }
}
override fun bindToStmt(
@@ -72,4 +79,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
index 2772728..be4fd45 100644
--- a/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -22,6 +22,7 @@
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runKaptTest
import androidx.room.compiler.processing.util.runProcessorTest
import androidx.room.ext.GuavaUtilConcurrentTypeNames
import androidx.room.ext.L
@@ -73,6 +74,62 @@
}
@Test
+ fun testInvalidNonStaticInnerClass() {
+ // TODO: (b/176180385)
+ val converter = Source.java(
+ "foo.bar.EmptyClass",
+ """
+ package foo.bar;
+ import androidx.room.*;
+ public class EmptyClass {
+ public enum Color {
+ RED,
+ GREEN
+ }
+ public class ColorTypeConverter {
+ @TypeConverter
+ public Color fromIntToColorEnum(int colorInt) {
+ if (colorInt == 1) {
+ return Color.RED;
+ } else {
+ return Color.GREEN;
+ }
+ }
+ }
+ }
+ """.trimIndent()
+ )
+ val entity = Source.java(
+ "foo.bar.EntityWithOneWayEnum",
+ """
+ package foo.bar;
+ import androidx.room.*;
+ @Entity
+ @TypeConverters(EmptyClass.ColorTypeConverter.class)
+ public class EntityWithOneWayEnum {
+ public enum Color {
+ RED,
+ GREEN
+ }
+ @PrimaryKey public Long id;
+ public Color color;
+ }
+ """.trimIndent()
+ )
+ runKaptTest(
+ sources = listOf(entity, converter)
+ ) { invocation ->
+ val typeElement =
+ invocation.processingEnv.requireTypeElement("foo.bar.EntityWithOneWayEnum")
+ val context = Context(invocation.processingEnv)
+ CustomConverterProcessor.Companion.findConverters(context, typeElement)
+ invocation.assertCompilationResult {
+ hasError(ProcessorErrors.INNER_CLASS_TYPE_CONVERTER_MUST_BE_STATIC)
+ }
+ }
+ }
+
+ @Test
fun testDirect() {
runProcessorTest { invocation ->
val store = TypeAdapterStore.create(Context(invocation.processingEnv))
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index 02be4b0..8cf85ec 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -116,6 +116,8 @@
androidTestImplementation(TRUTH)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it's own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it's own MockMaker
+ androidTestImplementation(project(":internal-testutils-truth"))
+
testImplementation(JUNIT)
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/AutoClosingRoomOpenHelperTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/AutoClosingRoomOpenHelperTest.java
new file mode 100644
index 0000000..8554527
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/AutoClosingRoomOpenHelperTest.java
@@ -0,0 +1,388 @@
+/*
+ * 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.room.integration.testapp.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.database.Cursor;
+
+import androidx.annotation.NonNull;
+import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.testing.TestLifecycleOwner;
+import androidx.room.InvalidationTracker;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.dao.UserDao;
+import androidx.room.integration.testapp.vo.User;
+import androidx.room.util.SneakyThrow;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.MediumTest;
+import androidx.testutils.AssertionsKt;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class AutoClosingRoomOpenHelperTest {
+ @Rule
+ public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
+ private UserDao mUserDao;
+ private TestDatabase mDb;
+ private final DatabaseCallbackTest.TestDatabaseCallback mCallback =
+ new DatabaseCallbackTest.TestDatabaseCallback();
+
+ @Before
+ public void createDb() throws TimeoutException, InterruptedException {
+ Context context = ApplicationProvider.getApplicationContext();
+ mDb = Room.databaseBuilder(context, TestDatabase.class, "testDb")
+ .setAutoCloseTimeout(10, TimeUnit.MILLISECONDS)
+ .addCallback(mCallback).build();
+ mUserDao = mDb.getUserDao();
+ drain();
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ mDb.clearAllTables();
+ mDb.close();
+ }
+
+ @Test
+ @MediumTest
+ public void inactiveConnection_shouldAutoClose() throws Exception {
+ assertFalse(mCallback.mOpened);
+ User user = TestUtil.createUser(1);
+ user.setName("bob");
+ mUserDao.insert(user);
+ assertTrue(mCallback.mOpened);
+
+ Thread.sleep(100);
+ // Connection should be auto closed here
+
+ User readUser = mUserDao.load(1);
+ assertEquals(readUser.getName(), user.getName());
+ }
+
+ @Test
+ @MediumTest
+ public void slowTransaction_keepsDbAlive() throws Exception {
+ assertFalse(mCallback.mOpened);
+
+ User user = TestUtil.createUser(1);
+ user.setName("bob");
+ mUserDao.insert(user);
+ assertTrue(mCallback.mOpened);
+ Thread.sleep(30);
+ mUserDao.load(1);
+ // Connection should be auto closed here
+
+
+ mDb.runInTransaction(
+ () -> {
+ try {
+ Thread.sleep(100);
+ // Connection would've been auto closed here
+ } catch (InterruptedException e) {
+ SneakyThrow.reThrow(e);
+ }
+ }
+ );
+
+ Thread.sleep(100);
+ // Connection should be auto closed here
+ }
+
+ @Test
+ @MediumTest
+ public void slowCursorClosing_keepsDbAlive() throws Exception {
+ assertFalse(mCallback.mOpened);
+ User user = TestUtil.createUser(1);
+ user.setName("bob");
+ mUserDao.insert(user);
+ assertTrue(mCallback.mOpened);
+ mUserDao.load(1);
+
+ Cursor cursor = mDb.query("select * from user", null);
+
+ Thread.sleep(100);
+
+ cursor.close();
+
+ Thread.sleep(100);
+ // Connection should be auto closed here
+ }
+
+ @Test
+ @MediumTest
+ public void autoClosedConnection_canReopen() throws Exception {
+ User user1 = TestUtil.createUser(1);
+ user1.setName("bob");
+ mUserDao.insert(user1);
+
+ Thread.sleep(100);
+ // Connection should be auto closed here
+
+ User user2 = TestUtil.createUser(2);
+ user2.setName("bob2");
+ mUserDao.insert(user2);
+ Thread.sleep(100);
+ // Connection should be auto closed here
+ }
+
+ @Test
+ @MediumTest
+ public void liveDataTriggers_shouldApplyOnReopen() throws Exception {
+ LiveData<Boolean> adminLiveData = mUserDao.isAdminLiveData(1);
+
+ final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+ final TestObserver<Boolean> observer = new AutoClosingRoomOpenHelperTest
+ .MyTestObserver<>();
+ TestUtil.observeOnMainThread(adminLiveData, lifecycleOwner, observer);
+ assertNull(observer.get());
+
+ User user = TestUtil.createUser(1);
+ user.setAdmin(true);
+ mUserDao.insert(user);
+
+ assertNotNull(observer.get());
+ assertTrue(observer.get());
+
+ user.setAdmin(false);
+ mUserDao.insertOrReplace(user);
+ assertNotNull(observer.get());
+ assertFalse(observer.get());
+
+ Thread.sleep(100);
+ // Connection should be auto closed here
+
+ user.setAdmin(true);
+ mUserDao.insertOrReplace(user);
+ assertNotNull(observer.get());
+ assertTrue(observer.get());
+ }
+
+ @Test
+ @MediumTest
+ public void testCanExecSqlInCallback() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+
+ mDb = Room.databaseBuilder(context, TestDatabase.class, "testDb")
+ .setAutoCloseTimeout(10, TimeUnit.MILLISECONDS)
+ .addCallback(new ExecSqlInCallback())
+ .build();
+
+ mDb.getUserDao().insert(TestUtil.createUser(1));
+ }
+
+ @Test
+ public void testManuallyRoomDatabaseClose() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+ // Create a new db since the other one is cleared in the @After
+ TestDatabase testDatabase = Room.databaseBuilder(context, TestDatabase.class, "testDb")
+ .setAutoCloseTimeout(10, TimeUnit.MILLISECONDS)
+ .addCallback(new ExecSqlInCallback())
+ .build();
+
+ testDatabase.close();
+
+ // We shouldn't be able to do anything with the database now...
+ AssertionsKt.assertThrows(IllegalStateException.class, () -> {
+ testDatabase.getUserDao().count();
+ }).hasMessageThat().contains("closed");
+
+ assertFalse(testDatabase.isOpen());
+
+ assertFalse(testDatabase.isOpen());
+ TestDatabase testDatabase2 = Room.databaseBuilder(context, TestDatabase.class, "testDb")
+ .setAutoCloseTimeout(10, TimeUnit.MILLISECONDS)
+ .addCallback(new ExecSqlInCallback())
+ .build();
+ testDatabase2.getUserDao().count(); // db should open now
+ testDatabase2.close();
+ assertFalse(testDatabase.isOpen());
+ }
+
+ @Test
+ public void testManuallyOpenHelperClose() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+ // Create a new db since the other one is cleared in the @After
+ TestDatabase testDatabase = Room.databaseBuilder(context, TestDatabase.class, "testDb")
+ .setAutoCloseTimeout(10, TimeUnit.MILLISECONDS)
+ .addCallback(new ExecSqlInCallback())
+ .build();
+
+ testDatabase.getOpenHelper().close();
+
+ // We shouldn't be able to do anything with the database now...
+ AssertionsKt.assertThrows(IllegalStateException.class, () -> {
+ testDatabase.getUserDao().count();
+ }).hasMessageThat().contains("closed");
+
+ assertFalse(testDatabase.isOpen());
+ TestDatabase testDatabase2 = Room.databaseBuilder(context, TestDatabase.class, "testDb")
+ .setAutoCloseTimeout(10, TimeUnit.MILLISECONDS)
+ .addCallback(new ExecSqlInCallback())
+ .build();
+ testDatabase2.getUserDao().count(); // db should open now
+ testDatabase2.getOpenHelper().close();
+ assertFalse(testDatabase.isOpen());
+ }
+
+ @Test
+ @MediumTest
+ public void invalidationObserver_isCalledOnEachInvalidation()
+ throws TimeoutException, InterruptedException {
+ AtomicInteger invalidationCount = new AtomicInteger(0);
+
+ UserTableObserver userTableObserver =
+ new UserTableObserver(invalidationCount::getAndIncrement);
+
+ mDb.getInvalidationTracker().addObserver(userTableObserver);
+
+ mUserDao.insert(TestUtil.createUser(1));
+
+ drain();
+ assertEquals(1, invalidationCount.get());
+
+ User user1 = TestUtil.createUser(1);
+ user1.setAge(123);
+ mUserDao.insertOrReplace(user1);
+
+ drain();
+ assertEquals(2, invalidationCount.get());
+
+ Thread.sleep(15);
+ // Connection should be closed now
+
+ mUserDao.insert(TestUtil.createUser(2));
+
+ drain();
+ assertEquals(3, invalidationCount.get());
+ }
+
+ @Test
+ @MediumTest
+ public void invalidationObserver_canRequeryDb() throws TimeoutException, InterruptedException {
+ Context context = ApplicationProvider.getApplicationContext();
+
+ context.deleteDatabase("testDb");
+ mDb = Room.databaseBuilder(context, TestDatabase.class, "testDb")
+ // create contention for callback
+ .setAutoCloseTimeout(0, TimeUnit.MILLISECONDS)
+ .addCallback(mCallback).build();
+
+ AtomicInteger userCount = new AtomicInteger(0);
+
+ UserTableObserver userTableObserver = new UserTableObserver(
+ () -> userCount.set(mUserDao.count()));
+
+ mDb.getInvalidationTracker().addObserver(userTableObserver);
+
+ mDb.getUserDao().insert(TestUtil.createUser(1));
+ mDb.getUserDao().insert(TestUtil.createUser(2));
+ mDb.getUserDao().insert(TestUtil.createUser(3));
+ mDb.getUserDao().insert(TestUtil.createUser(4));
+ mDb.getUserDao().insert(TestUtil.createUser(5));
+ mDb.getUserDao().insert(TestUtil.createUser(6));
+ mDb.getUserDao().insert(TestUtil.createUser(7));
+
+ drain();
+ assertEquals(7, userCount.get());
+ }
+
+ @Test
+ @MediumTest
+ public void invalidationObserver_notifiedByTableName() throws TimeoutException,
+ InterruptedException {
+ Context context = ApplicationProvider.getApplicationContext();
+
+ context.deleteDatabase("testDb");
+ mDb = Room.databaseBuilder(context, TestDatabase.class, "testDb")
+ // create contention for callback
+ .setAutoCloseTimeout(0, TimeUnit.MILLISECONDS)
+ .addCallback(mCallback).build();
+
+ AtomicInteger invalidationCount = new AtomicInteger(0);
+
+ UserTableObserver userTableObserver =
+ new UserTableObserver(invalidationCount::getAndIncrement);
+
+ mDb.getInvalidationTracker().addObserver(userTableObserver);
+
+
+ mDb.getUserDao().insert(TestUtil.createUser(1));
+
+ drain();
+ assertEquals(1, invalidationCount.get());
+
+ Thread.sleep(100); // Let db auto close
+
+ mDb.getInvalidationTracker().notifyObserversByTableNames("user");
+
+ drain();
+ assertEquals(2, invalidationCount.get());
+
+ }
+
+ private void drain() throws TimeoutException, InterruptedException {
+ mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+ }
+
+ private class MyTestObserver<T> extends TestObserver<T> {
+ @Override
+ protected void drain() throws TimeoutException, InterruptedException {
+ AutoClosingRoomOpenHelperTest.this.drain();
+ }
+ }
+
+ private static class ExecSqlInCallback extends RoomDatabase.Callback {
+ @Override
+ public void onOpen(@NonNull SupportSQLiteDatabase db) {
+ db.query("select * from user").close();
+ }
+ }
+
+ private static class UserTableObserver extends InvalidationTracker.Observer {
+
+ private final Runnable mInvalidationCallback;
+
+ UserTableObserver(Runnable invalidationCallback) {
+ super("user");
+ mInvalidationCallback = invalidationCallback;
+ }
+
+ @Override
+ public void onInvalidated(@NonNull @NotNull Set<String> tables) {
+ mInvalidationCallback.run();
+ }
+ }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ByteBufferColumnTypeAdapterTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ByteBufferColumnTypeAdapterTest.java
new file mode 100644
index 0000000..fba5310
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/ByteBufferColumnTypeAdapterTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.room.integration.testapp.test;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Entity;
+import androidx.room.Insert;
+import androidx.room.PrimaryKey;
+import androidx.room.Query;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ByteBufferColumnTypeAdapterTest {
+
+ private ByteBufferColumnTypeAdapterDatabase mDb;
+
+ @Entity(tableName = "byteBufferFoo")
+ public static class ByteBufferFoo {
+ @PrimaryKey @NonNull public String id;
+ public ByteBuffer buffer;
+
+ public ByteBufferFoo(@NotNull String id, ByteBuffer buffer) {
+ this.id = id;
+ this.buffer = buffer;
+ }
+ }
+
+ @Dao
+ public interface ByteBufferFooDao {
+ @Query("select * from ByteBufferFoo where id = :id")
+ ByteBufferFoo getItem(String id);
+
+ @Insert
+ void insert(ByteBufferFoo item);
+ }
+
+ @Database(
+ version = 1,
+ entities = {
+ ByteBufferFoo.class
+ },
+ exportSchema = false
+ )
+ public abstract static class ByteBufferColumnTypeAdapterDatabase extends RoomDatabase {
+ public abstract ByteBufferFooDao byteBufferFooDao();
+ }
+
+ @Test
+ public void testByteBufferFooDao() {
+ Context context = ApplicationProvider.getApplicationContext();
+ mDb = Room.inMemoryDatabaseBuilder(
+ context,
+ ByteBufferColumnTypeAdapterDatabase.class)
+ .build();
+
+ mDb.byteBufferFooDao().insert(new ByteBufferFoo("Key1", null));
+ assertThat(mDb.byteBufferFooDao().getItem("Key1").buffer).isEqualTo(null);
+ }
+}
diff --git a/room/runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt b/room/runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt
index 87e9c17..bee72dd 100644
--- a/room/runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt
+++ b/room/runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt
@@ -79,6 +79,7 @@
autoCloseExecutor
).also {
it.init(delegateOpenHelper)
+ it.setAutoCloseCallback { }
}
}
@@ -186,4 +187,23 @@
Thread.sleep(5)
countingTaskExecutorRule.drainTasks(10, TimeUnit.MILLISECONDS)
}
+
+ @Test
+ public fun testDbCanBeManuallyClosed() {
+ val db = autoCloser.incrementCountAndEnsureDbIsOpen()
+
+ assertThat(db.isOpen).isTrue()
+
+ autoCloser.closeDatabaseIfOpen() // Should succeed...
+
+ assertThat(db.isOpen).isFalse()
+
+ assertThrows<IllegalStateException> { db.execSQL("select * from users") }
+
+ autoCloser.decrementCountAndScheduleClose() // Should succeed
+
+ assertThrows<IllegalStateException> {
+ autoCloser.incrementCountAndEnsureDbIsOpen()
+ }
+ }
}
\ No newline at end of file
diff --git a/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperFactoryTest.kt b/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperFactoryTest.kt
index febc98c..9603a47 100644
--- a/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperFactoryTest.kt
+++ b/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperFactoryTest.kt
@@ -45,6 +45,7 @@
return AutoClosingRoomOpenHelperFactory(
delegateOpenHelperFactory,
AutoCloser(timeoutMillis, TimeUnit.MILLISECONDS, Executors.newSingleThreadExecutor())
+ .also { it.mOnAutoCloseCallback = Runnable {} }
)
}
diff --git a/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt b/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
index c601d2e..d673e4c 100644
--- a/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
+++ b/room/runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
@@ -69,7 +69,10 @@
return AutoClosingRoomOpenHelper(
delegateOpenHelper,
- AutoCloser(timeoutMillis, TimeUnit.MILLISECONDS, autoCloseExecutor)
+ AutoCloser(timeoutMillis, TimeUnit.MILLISECONDS, autoCloseExecutor).apply {
+ init(delegateOpenHelper)
+ setAutoCloseCallback { }
+ }
)
}
diff --git a/room/runtime/src/main/java/androidx/room/AutoCloser.java b/room/runtime/src/main/java/androidx/room/AutoCloser.java
index eda2363..ba157ec 100644
--- a/room/runtime/src/main/java/androidx/room/AutoCloser.java
+++ b/room/runtime/src/main/java/androidx/room/AutoCloser.java
@@ -50,6 +50,10 @@
private final Handler mHandler = new Handler(Looper.getMainLooper());
// Package private for access from mAutoCloser
+ @Nullable
+ Runnable mOnAutoCloseCallback = null;
+
+ // Package private for access from mAutoCloser
@NonNull
final Object mLock = new Object();
@@ -74,6 +78,8 @@
@Nullable
SupportSQLiteDatabase mDelegateDatabase;
+ private boolean mManuallyClosed = false;
+
private final Runnable mExecuteAutoCloser = new Runnable() {
@Override
public void run() {
@@ -102,7 +108,16 @@
return;
}
- if (mDelegateDatabase != null) {
+ if (mOnAutoCloseCallback != null) {
+ mOnAutoCloseCallback.run();
+ } else {
+ throw new IllegalStateException("mOnAutoCloseCallback is null but it should"
+ + " have been set before use. Please file a bug "
+ + "against Room at: https://issuetracker.google"
+ + ".com/issues/new?component=413107&template=1096568");
+ }
+
+ if (mDelegateDatabase != null && mDelegateDatabase.isOpen()) {
try {
mDelegateDatabase.close();
} catch (IOException e) {
@@ -138,8 +153,9 @@
*/
public void init(@NonNull SupportSQLiteOpenHelper delegateOpenHelper) {
if (mDelegateOpenHelper != null) {
- Log.e(Room.LOG_TAG, "AutoCloser initialized multiple times. This is probably a bug in"
- + " the room code.");
+ Log.e(Room.LOG_TAG, "AutoCloser initialized multiple times. Please file a bug against"
+ + " room at: https://issuetracker.google"
+ + ".com/issues/new?component=413107&template=1096568");
return;
}
this.mDelegateOpenHelper = delegateOpenHelper;
@@ -182,11 +198,12 @@
mRefCount++;
+ if (mManuallyClosed) {
+ throw new IllegalStateException("Attempting to open already closed database.");
+ }
+
if (mDelegateDatabase != null && mDelegateDatabase.isOpen()) {
return mDelegateDatabase;
- } else if (mDelegateDatabase != null) {
- // This shouldn't happen
- throw new IllegalStateException("mDelegateDatabase is closed but non-null");
}
// Get the database while holding `mLock` so no other threads try to create it or
@@ -194,8 +211,9 @@
if (mDelegateOpenHelper != null) {
mDelegateDatabase = mDelegateOpenHelper.getWritableDatabase();
} else {
- throw new IllegalStateException("AutoCloser has not beeninitialized. This "
- + "shouldn't happen, but if it does it means there's a bug in our code");
+ throw new IllegalStateException("AutoCloser has not been initialized. Please file "
+ + "a bug against Room at: "
+ + "https://issuetracker.google.com/issues/new?component=413107&template=1096568");
}
return mDelegateDatabase;
@@ -243,6 +261,33 @@
}
/**
+ * Close the database if it is still active.
+ *
+ * @throws IOException if an exception is encountered when closing the underlying db.
+ */
+ public void closeDatabaseIfOpen() throws IOException {
+ synchronized (mLock) {
+ mManuallyClosed = true;
+
+ if (mDelegateDatabase != null) {
+ mDelegateDatabase.close();
+ }
+ mDelegateDatabase = null;
+ }
+ }
+
+ /**
+ * The auto closer is still active if the database has not been closed. This means that
+ * whether or not the underlying database is closed, when active we will re-open it on the
+ * next access.
+ *
+ * @return a boolean indicating whether the auto closer is still active
+ */
+ public boolean isActive() {
+ return !mManuallyClosed;
+ }
+
+ /**
* Returns the current ref count for this auto closer. This is only visible for testing.
*
* @return current ref count
@@ -253,4 +298,14 @@
return mRefCount;
}
}
+
+ /**
+ * 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
+ */
+ public void setAutoCloseCallback(Runnable onAutoClose) {
+ mOnAutoCloseCallback = onAutoClose;
+ }
}
diff --git a/room/runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.java b/room/runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.java
index 9aabf95..b3a3853 100644
--- a/room/runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.java
+++ b/room/runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.java
@@ -35,6 +35,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.arch.core.util.Function;
+import androidx.room.util.SneakyThrow;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import androidx.sqlite.db.SupportSQLiteQuery;
@@ -100,7 +101,11 @@
@Override
public void close() {
- mAutoClosingDb.close();
+ try {
+ mAutoClosingDb.close();
+ } catch (IOException e) {
+ SneakyThrow.reThrow(e);
+ }
}
/**
@@ -483,12 +488,8 @@
}
@Override
- public void close() {
- //TODO: I might want to handle a manual close call here differently because as it is
- // implemented right now, if someone calls close() then calls another method here,
- // it'll automatically reopen the db, which may not be expected. However, having a
- // single instance here makes this simpler.
- throw new IllegalStateException("can't be manually closed yet");
+ public void close() throws IOException {
+ mAutoCloser.closeDatabaseIfOpen();
}
}
diff --git a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
index 927d6ae..7dda1da 100644
--- a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
+++ b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
@@ -90,6 +90,9 @@
@NonNull
private Map<String, Set<String>> mViewTables;
+ @Nullable
+ AutoCloser mAutoCloser = null;
+
@SuppressWarnings("WeakerAccess") /* synthetic access */
final RoomDatabase mDatabase;
@@ -161,6 +164,23 @@
}
/**
+ * Sets the auto closer for this invalidation tracker so that the invalidation tracker can
+ * ensure that the database is not closed if there are pending invalidations that haven't yet
+ * been flushed.
+ *
+ * This also adds a callback to the autocloser to ensure that the InvalidationTracker is in
+ * an ok state once the table is invalidated.
+ *
+ * This must be called before the database is used.
+ *
+ * @param autoCloser the autocloser associated with the db
+ */
+ void setAutoCloser(AutoCloser autoCloser) {
+ this.mAutoCloser = autoCloser;
+ mAutoCloser.setAutoCloseCallback(this::onAutoCloseCallback);
+ }
+
+ /**
* Internal method to initialize table tracking.
* <p>
* You should never call this method, it is called by the generated code.
@@ -183,6 +203,13 @@
}
}
+ void onAutoCloseCallback() {
+ synchronized (this) {
+ mInitialized = false;
+ mObservedTableTracker.resetTriggerState();
+ }
+ }
+
void startMultiInstanceInvalidation(Context context, String name) {
mMultiInstanceInvalidationClient = new MultiInstanceInvalidationClient(context, name, this,
mDatabase.getQueryExecutor());
@@ -415,6 +442,10 @@
exception);
} finally {
closeLock.unlock();
+
+ if (mAutoCloser != null) {
+ mAutoCloser.decrementCountAndScheduleClose();
+ }
}
if (invalidatedTableIds != null && !invalidatedTableIds.isEmpty()) {
synchronized (mObserverMap) {
@@ -455,6 +486,13 @@
public void refreshVersionsAsync() {
// TODO we should consider doing this sync instead of async.
if (mPendingRefresh.compareAndSet(false, true)) {
+ if (mAutoCloser != null) {
+ // refreshVersionsAsync is called with the ref count incremented from
+ // RoomDatabase, so the db can't be closed here, but we need to be sure that our
+ // db isn't closed until refresh is completed. This increment call must be
+ // matched with a corresponding call in mRefreshRunnable.
+ mAutoCloser.incrementCountAndEnsureDbIsOpen();
+ }
mDatabase.getQueryExecutor().execute(mRefreshRunnable);
}
}
@@ -467,6 +505,10 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@WorkerThread
public void refreshVersionsSync() {
+ if (mAutoCloser != null) {
+ // This increment call must be matched with a corresponding call in mRefreshRunnable.
+ mAutoCloser.incrementCountAndEnsureDbIsOpen();
+ }
syncTriggers();
mRefreshRunnable.run();
}
@@ -802,6 +844,17 @@
}
/**
+ * If we are re-opening the db we'll need to add all the triggers that we need so change
+ * the current state to false for all.
+ */
+ void resetTriggerState() {
+ synchronized (this) {
+ Arrays.fill(mTriggerStates, false);
+ mNeedsSync = true;
+ }
+ }
+
+ /**
* If this returns non-null, you must call onSyncCompleted.
*
* @return int[] An int array where the index for each tableId has the action for that
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
index 0fa15d8..f94bd74 100644
--- a/room/runtime/src/main/java/androidx/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -54,6 +54,7 @@
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -99,6 +100,9 @@
private final ReentrantReadWriteLock mCloseLock = new ReentrantReadWriteLock();
+ @Nullable
+ private AutoCloser mAutoCloser;
+
/**
* {@link InvalidationTracker} uses this lock to prevent the database from closing while it is
* querying database updates.
@@ -187,6 +191,15 @@
copyOpenHelper.setDatabaseConfiguration(configuration);
}
+ AutoClosingRoomOpenHelper autoClosingRoomOpenHelper =
+ unwrapOpenHelper(AutoClosingRoomOpenHelper.class, mOpenHelper);
+
+ if (autoClosingRoomOpenHelper != null) {
+ mAutoCloser = autoClosingRoomOpenHelper.getAutoCloser();
+ mInvalidationTracker.setAutoCloser(mAutoCloser);
+ }
+
+
boolean wal = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
@@ -329,6 +342,12 @@
* @return true if the database connection is open, false otherwise.
*/
public boolean isOpen() {
+ // We need to special case for the auto closing database because mDatabase is the
+ // underlying database and not the wrapped database.
+ if (mAutoCloser != null) {
+ return mAutoCloser.isActive();
+ }
+
final SupportSQLiteDatabase db = mDatabase;
return db != null && db.isOpen();
}
@@ -447,6 +466,18 @@
@Deprecated
public void beginTransaction() {
assertNotMainThread();
+ if (mAutoCloser == null) {
+ internalBeginTransaction();
+ } else {
+ mAutoCloser.executeRefCountingFunction(db -> {
+ internalBeginTransaction();
+ return null;
+ });
+ }
+ }
+
+ private void internalBeginTransaction() {
+ assertNotMainThread();
SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
mInvalidationTracker.syncTriggers(database);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
@@ -464,6 +495,17 @@
*/
@Deprecated
public void endTransaction() {
+ if (mAutoCloser == null) {
+ internalEndTransaction();
+ } else {
+ mAutoCloser.executeRefCountingFunction(db -> {
+ internalEndTransaction();
+ return null;
+ });
+ }
+ }
+
+ private void internalEndTransaction() {
mOpenHelper.getWritableDatabase().endTransaction();
if (!inTransaction()) {
// enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
@@ -658,6 +700,10 @@
private boolean mMultiInstanceInvalidation;
private boolean mRequireMigration;
private boolean mAllowDestructiveMigrationOnDowngrade;
+
+ private long mAutoCloseTimeout = -1L;
+ private TimeUnit mAutoCloseTimeUnit;
+
/**
* Migrations, mapped by from-to pairs.
*/
@@ -1155,6 +1201,50 @@
}
/**
+ * Enables auto-closing for the database to free up unused resources. The underlying
+ * database will be closed after it's last use after the specified {@code
+ * autoCloseTimeout} has elapsed since its last usage. The database will be automatically
+ * re-opened the next time it is accessed.
+ * <p>
+ * Auto-closing is not compatible with in-memory databases since the data will be lost
+ * when the database is auto-closed.
+ * <p>
+ * Also, temp tables and temp triggers will be cleared each time the database is
+ * auto-closed. If you need to use them, please include them in your
+ * {@link RoomDatabase.Callback.OnOpen callback}.
+ * <p>
+ * All configuration should happen in your {@link RoomDatabase.Callback.onOpen}
+ * callback so it is re-applied every time the database is re-opened. Note that the
+ * {@link RoomDatabase.Callback.onOpen} will be called every time the database is re-opened.
+ * <p>
+ * The auto-closing database operation runs on the query executor.
+ * <p>
+ * The database will not be reopened if the RoomDatabase or the
+ * SupportSqliteOpenHelper is closed manually (by calling
+ * {@link RoomDatabase.close()} or {@link SupportSQLiteOpenHelper.close()}. If the
+ * database is closed manually, you must create a new database using
+ * {@link RoomDatabase.Builder.build()}.
+ *
+ * @param autoCloseTimeout the amount of time after the last usage before closing the
+ * database
+ * @param autoCloseTimeUnit the timeunit for autoCloseTimeout.
+ * @return This {@link Builder} instance
+ *
+ * @hide until it's ready for use
+ */
+ @NonNull
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public Builder<T> setAutoCloseTimeout(long autoCloseTimeout,
+ @NonNull TimeUnit autoCloseTimeUnit) {
+ if (autoCloseTimeout < 0) {
+ throw new IllegalArgumentException("autoCloseTimeout must be >= 0");
+ }
+ mAutoCloseTimeout = autoCloseTimeout;
+ mAutoCloseTimeUnit = autoCloseTimeUnit;
+ return this;
+ }
+
+ /**
* Creates the databases and initializes it.
* <p>
* By default, all RoomDatabases use in memory storage for TEMP tables and enables recursive
@@ -1198,12 +1288,26 @@
SupportSQLiteOpenHelper.Factory factory;
+ AutoCloser autoCloser = null;
+
if (mFactory == null) {
factory = new FrameworkSQLiteOpenHelperFactory();
} else {
factory = mFactory;
}
+ if (mAutoCloseTimeout > 0) {
+ if (mName == null) {
+ throw new IllegalArgumentException("Cannot create auto-closing database for "
+ + "an in-memory database.");
+ }
+
+ autoCloser = new AutoCloser(mAutoCloseTimeout, mAutoCloseTimeUnit,
+ mTransactionExecutor);
+
+ factory = new AutoClosingRoomOpenHelperFactory(factory, autoCloser);
+ }
+
if (mCopyFromAssetPath != null
|| mCopyFromFile != null
|| mCopyFromInputStream != null) {
diff --git a/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt b/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt
index d5f3b92..8bef533 100644
--- a/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt
+++ b/testutils/testutils-paging/src/main/java/androidx/paging/TestPagingSource.kt
@@ -54,7 +54,8 @@
val isPrepend = params is LoadParams.Prepend
val start = if (isPrepend) key - params.loadSize + 1 else key
- val end = if (isPrepend) key + 1 else key + params.loadSize
+ var end = if (isPrepend) key + 1 else key + params.loadSize
+ end = minOf(end, items.size)
// This delay allows tests running withing DelayController APIs to control the order of
// execution of events.
diff --git a/wear/wear-watchface-complications-rendering/api/current.txt b/wear/wear-watchface-complications-rendering/api/current.txt
index 5cab738b..1e136b6 100644
--- a/wear/wear-watchface-complications-rendering/api/current.txt
+++ b/wear/wear-watchface-complications-rendering/api/current.txt
@@ -23,7 +23,7 @@
method public void setAlpha(int);
method public void setBurnInProtection(boolean);
method public void setColorFilter(android.graphics.ColorFilter?);
- method public void setComplicationData(android.support.wearable.complications.ComplicationData?);
+ method public void setComplicationData(android.support.wearable.complications.ComplicationData?, boolean);
method public void setContext(android.content.Context);
method public void setCurrentTimeMillis(long);
method public void setHighlightDuration(@IntRange(from=0) long);
diff --git a/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt b/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
index 5cab738b..1e136b6 100644
--- a/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
@@ -23,7 +23,7 @@
method public void setAlpha(int);
method public void setBurnInProtection(boolean);
method public void setColorFilter(android.graphics.ColorFilter?);
- method public void setComplicationData(android.support.wearable.complications.ComplicationData?);
+ method public void setComplicationData(android.support.wearable.complications.ComplicationData?, boolean);
method public void setContext(android.content.Context);
method public void setCurrentTimeMillis(long);
method public void setHighlightDuration(@IntRange(from=0) long);
diff --git a/wear/wear-watchface-complications-rendering/api/restricted_current.txt b/wear/wear-watchface-complications-rendering/api/restricted_current.txt
index c42224a..3fc7fc4 100644
--- a/wear/wear-watchface-complications-rendering/api/restricted_current.txt
+++ b/wear/wear-watchface-complications-rendering/api/restricted_current.txt
@@ -23,7 +23,7 @@
method public void setAlpha(int);
method public void setBurnInProtection(boolean);
method public void setColorFilter(android.graphics.ColorFilter?);
- method public void setComplicationData(android.support.wearable.complications.ComplicationData?);
+ method public void setComplicationData(android.support.wearable.complications.ComplicationData?, boolean);
method public void setContext(android.content.Context);
method public void setCurrentTimeMillis(long);
method public void setHighlightDuration(@IntRange(from=0) long);
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java
index b455604..569fcae 100644
--- a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java
+++ b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java
@@ -300,7 +300,7 @@
* Sets the context used to render the complication. If a context is not set,
* ComplicationDrawable will throw an {@link IllegalStateException} if one of
* {@link #draw(Canvas)}, {@link #setBounds(Rect)}, or {@link
- * #setComplicationData(ComplicationData)} is called.
+ * #setComplicationData(ComplicationData, boolean)} is called.
*
* <p>While this can be called from any context, ideally, a
* androidx.wear.watchface.WatchFaceService object should be passed here to allow creating
@@ -619,10 +619,17 @@
/**
* Sets the complication data to be drawn. If {@code complicationData} is {@code null}, nothing
* will be drawn when {@link #draw(Canvas)} is called.
+ *
+ * @param complicationData The [ComplicationData] to set
+ * @param loadDrawablesAsync If true any drawables should be loaded asynchronously,
+ * otherwise they will be loaded synchronously.
*/
- public void setComplicationData(@Nullable ComplicationData complicationData) {
+ public void setComplicationData(
+ @Nullable ComplicationData complicationData,
+ boolean loadDrawablesAsync
+ ) {
assertInitialized();
- mComplicationRenderer.setComplicationData(complicationData);
+ mComplicationRenderer.setComplicationData(complicationData, loadDrawablesAsync);
}
/**
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationRenderer.java b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationRenderer.java
index 2824f40..1445266 100644
--- a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationRenderer.java
+++ b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationRenderer.java
@@ -209,8 +209,10 @@
* Sets the complication data to be rendered.
*
* @param data Complication data to be rendered. If this is null, nothing is drawn.
+ * @param loadDrawablesAsync If true any drawables will be loaded asynchronously, otherwise
+ * they will be loaded synchronously.
*/
- public void setComplicationData(@Nullable ComplicationData data) {
+ public void setComplicationData(@Nullable ComplicationData data, boolean loadDrawablesAsync) {
if (Objects.equals(mComplicationData, data)) {
return;
}
@@ -234,8 +236,12 @@
mComplicationData = data;
mHasNoData = false;
}
- if (!loadDrawableIconAndImages()) {
- invalidate();
+ if (loadDrawablesAsync) {
+ if (!loadDrawableIconAndImagesAsync()) {
+ invalidate();
+ }
+ } else {
+ loadDrawableIconAndImages();
}
calculateBounds();
}
@@ -274,7 +280,7 @@
if (mHasNoData) {
mHasNoData = false;
setComplicationData(
- new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build());
+ new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build(), true);
}
}
@@ -701,7 +707,7 @@
* Returns true if the data contains images. If there are, the images will be loaded
* asynchronously and the drawable will be invalidated when loading is complete.
*/
- private boolean loadDrawableIconAndImages() {
+ private boolean loadDrawableIconAndImagesAsync() {
Handler handler = new Handler(Looper.getMainLooper());
Icon icon = null;
Icon smallImage = null;
@@ -821,6 +827,52 @@
return hasImage;
}
+ /** Synchronously loads any images. */
+ private void loadDrawableIconAndImages() {
+ Icon icon = null;
+ Icon smallImage = null;
+ Icon burnInProtectionSmallImage = null;
+ Icon largeImage = null;
+ Icon burnInProtectionIcon = null;
+ mIcon = null;
+ mSmallImage = null;
+ mBurnInProtectionSmallImage = null;
+ mLargeImage = null;
+ mBurnInProtectionIcon = null;
+ if (mComplicationData != null) {
+ icon = mComplicationData.hasIcon() ? mComplicationData.getIcon() : null;
+ burnInProtectionIcon = mComplicationData.hasBurnInProtectionIcon()
+ ? mComplicationData.getBurnInProtectionIcon() : null;
+ burnInProtectionSmallImage =
+ mComplicationData.hasBurnInProtectionSmallImage()
+ ? mComplicationData.getBurnInProtectionSmallImage() : null;
+ smallImage =
+ mComplicationData.hasSmallImage() ? mComplicationData.getSmallImage() : null;
+ largeImage =
+ mComplicationData.hasLargeImage() ? mComplicationData.getLargeImage() : null;
+ }
+
+ if (icon != null) {
+ mIcon = icon.loadDrawable(mContext);
+ }
+
+ if (burnInProtectionIcon != null) {
+ mBurnInProtectionIcon = burnInProtectionIcon.loadDrawable(mContext);
+ }
+
+ if (smallImage != null) {
+ mSmallImage = smallImage.loadDrawable(mContext);
+ }
+
+ if (burnInProtectionSmallImage != null) {
+ mBurnInProtectionSmallImage = burnInProtectionSmallImage.loadDrawable(mContext);
+ }
+
+ if (largeImage != null) {
+ mLargeImage = largeImage.loadDrawable(mContext);
+ }
+ }
+
@VisibleForTesting
static class PaintSet {
diff --git a/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java b/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
index 91c08a4..cdb4705 100644
--- a/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
+++ b/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationDrawableTest.java
@@ -135,7 +135,7 @@
public void callingSetComplicationDataBeforeSetContextThrowsAnException() {
assertThrows(
IllegalStateException.class,
- () -> mComplicationDrawable.setComplicationData(mComplicationData));
+ () -> mComplicationDrawable.setComplicationData(mComplicationData, true));
}
@Test
@@ -168,7 +168,7 @@
mComplicationDrawable.setContext(ApplicationProvider.getApplicationContext());
// AND below methods are called afterwards
mComplicationDrawable.draw(mMockCanvas);
- mComplicationDrawable.setComplicationData(mComplicationData);
+ mComplicationDrawable.setComplicationData(mComplicationData, true);
// THEN no exception is thrown
}
@@ -373,7 +373,7 @@
@Test
public void onTapReturnsFalseIfNoComplicationData() {
mComplicationDrawable.setContext(ApplicationProvider.getApplicationContext());
- mComplicationDrawable.setComplicationData(null);
+ mComplicationDrawable.setComplicationData(null, true);
mComplicationDrawable.setBounds(new Rect(0, 0, 100, 100));
assertThat(mComplicationDrawable.onTap(50, 50)).isFalse();
@@ -386,7 +386,8 @@
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText("rofl"))
.setShortTitle(ComplicationText.plainText("copter"))
- .build());
+ .build(),
+ true);
mComplicationDrawable.setBounds(new Rect(0, 0, 100, 100));
assertThat(mComplicationDrawable.onTap(50, 50)).isFalse();
@@ -400,7 +401,8 @@
.setShortText(ComplicationText.plainText("rofl"))
.setShortTitle(ComplicationText.plainText("copter"))
.setTapAction(mMockPendingIntent)
- .build());
+ .build(),
+ true);
mComplicationDrawable.setBounds(new Rect(0, 0, 100, 100));
assertThat(mComplicationDrawable.onTap(200, 200)).isFalse();
@@ -416,7 +418,8 @@
.setShortText(ComplicationText.plainText("rofl"))
.setShortTitle(ComplicationText.plainText("copter"))
.setTapAction(mMockPendingIntent)
- .build());
+ .build(),
+ true);
mComplicationDrawable.setBounds(new Rect(0, 0, 100, 100));
assertThat(mComplicationDrawable.onTap(50, 50)).isFalse();
@@ -430,7 +433,8 @@
.setShortText(ComplicationText.plainText("rofl"))
.setShortTitle(ComplicationText.plainText("copter"))
.setTapAction(mMockPendingIntent)
- .build());
+ .build(),
+ true);
reset(mMockDrawableCallback);
mComplicationDrawable.setBounds(new Rect(0, 0, 100, 100));
@@ -448,7 +452,8 @@
.setShortText(ComplicationText.plainText("rofl"))
.setShortTitle(ComplicationText.plainText("copter"))
.setTapAction(mMockPendingIntent)
- .build());
+ .build(),
+ true);
reset(mMockDrawableCallback);
mComplicationDrawable.setBounds(new Rect(0, 0, 100, 100));
@@ -477,7 +482,8 @@
.setShortText(ComplicationText.plainText("rofl"))
.setShortTitle(ComplicationText.plainText("copter"))
.setTapAction(mMockPendingIntent)
- .build());
+ .build(),
+ true);
mComplicationDrawable.setBounds(new Rect(0, 0, 100, 100));
mComplicationDrawable.setHighlightDuration(highlightDuration);
@@ -511,7 +517,7 @@
mComplicationDrawable.setBounds(new Rect(0, 0, 100, 100));
mComplicationDrawable.setComplicationData(
- new ComplicationData.Builder(ComplicationData.TYPE_NO_PERMISSION).build());
+ new ComplicationData.Builder(ComplicationData.TYPE_NO_PERMISSION).build(), true);
assertThat(mComplicationDrawable.onTap(50, 50)).isTrue();
@@ -533,7 +539,7 @@
mComplicationDrawable.setBounds(new Rect(0, 0, 100, 100));
mComplicationDrawable.setComplicationData(
- new ComplicationData.Builder(ComplicationData.TYPE_NO_PERMISSION).build());
+ new ComplicationData.Builder(ComplicationData.TYPE_NO_PERMISSION).build(), true);
assertThat(mComplicationDrawable.onTap(50, 50)).isFalse();
diff --git a/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java b/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
index 93d53ee..74673ca 100644
--- a/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
+++ b/wear/wear-watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
@@ -105,7 +105,7 @@
String noDataText = "No data";
mComplicationRenderer.setNoDataText(noDataText);
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build());
+ new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build(), true);
assertThat(mComplicationRenderer.getComplicationData().getType())
.isEqualTo(ComplicationData.TYPE_SHORT_TEXT);
assertThat(mComplicationRenderer
@@ -122,7 +122,7 @@
mComplicationRenderer.setNoDataText(firstText);
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build());
+ new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build(), true);
CharSequence firstResult =
mComplicationRenderer
.getComplicationData()
@@ -149,7 +149,7 @@
mComplicationRenderer.setNoDataText(text);
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build());
+ new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build(), true);
CharSequence firstResult =
mComplicationRenderer
.getComplicationData()
@@ -178,7 +178,7 @@
mComplicationRenderer.setNoDataText(text);
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build());
+ new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build(), true);
CharSequence firstResult =
mComplicationRenderer
.getComplicationData()
@@ -188,7 +188,7 @@
text.setSpan(blueSpan, 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build());
+ new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA).build(), true);
CharSequence secondResult =
mComplicationRenderer
.getComplicationData()
@@ -203,7 +203,7 @@
public void componentBoundsAreNotRecalculatedWhenSizeDoesNotChange() {
// GIVEN an icon type complication and bounds
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build());
+ new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build(), true);
// WHEN complication size changes
mComplicationBounds.inset(10, 10);
boolean firstCallResult = mComplicationRenderer.setBounds(mComplicationBounds);
@@ -220,7 +220,7 @@
public void paintSetsAreReinitializedWhenAmbientPropertiesChange() {
// GIVEN a complication renderer
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build());
+ new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build(), true);
// WHEN complication is drawn in ambient mode
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, true, true, true, false);
ComplicationRenderer.PaintSet firstPaintSet = mComplicationRenderer.mAmbientPaintSet;
@@ -262,7 +262,7 @@
ambientStyle.setBorderColor(Color.CYAN);
mComplicationRenderer.updateStyle(activeStyle, ambientStyle);
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build());
+ new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build(), true);
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, true, true, true, false);
ComplicationRenderer.PaintSet paintSet = mComplicationRenderer.mAmbientPaintSet;
@@ -287,7 +287,7 @@
ambientStyle.setBorderColor(Color.TRANSPARENT);
mComplicationRenderer.updateStyle(activeStyle, ambientStyle);
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build());
+ new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build(), true);
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, true, true, true, false);
ComplicationRenderer.PaintSet paintSet = mComplicationRenderer.mAmbientPaintSet;
@@ -304,12 +304,12 @@
public void nothingIsDrawnForEmptyAndNotConfiguredTypes() {
// GIVEN a complication renderer with not configured data
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(ComplicationData.TYPE_NOT_CONFIGURED).build());
+ new ComplicationData.Builder(ComplicationData.TYPE_NOT_CONFIGURED).build(), true);
// WHEN complication is drawn onto a canvas
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, false, false, false, false);
// AND complication data is changed to empty
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(ComplicationData.TYPE_EMPTY).build());
+ new ComplicationData.Builder(ComplicationData.TYPE_EMPTY).build(), true);
// AND complication is drawn again
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, false, false, false, false);
// THEN nothing is drawn on canvas
@@ -323,7 +323,8 @@
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText("hello"))
.setEndDateTimeMillis(REFERENCE_TIME - 100)
- .build());
+ .build(),
+ true);
// WHEN complication is drawn onto a canvas
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, false, false, false, false);
@@ -340,7 +341,8 @@
.setShortText(ComplicationText.plainText("hello"))
.setStartDateTimeMillis(REFERENCE_TIME - 5000)
.setEndDateTimeMillis(REFERENCE_TIME + 2000)
- .build());
+ .build(),
+ true);
// WHEN complication is drawn onto a canvas
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, false, false, false, false);
@@ -415,7 +417,8 @@
.setRangedValue(value)
.setRangedMinValue(min)
.setRangedMaxValue(max)
- .build());
+ .build(),
+ true);
// WHEN the complication is drawn in active mode
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, false, false, false, false);
int gap = ComplicationRenderer.STROKE_GAP_IN_DEGREES;
@@ -448,7 +451,8 @@
.setRangedValue(value)
.setRangedMinValue(min)
.setRangedMaxValue(max)
- .build());
+ .build(),
+ true);
// WHEN the complication is drawn in ambient mode
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, true, false, false, false);
int gap = ComplicationRenderer.STROKE_GAP_IN_DEGREES;
@@ -476,7 +480,8 @@
mComplicationRenderer.setComplicationData(
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText("Test text"))
- .build());
+ .build(),
+ true);
// AND with a style that has borders and border radius
float radius = 5;
int borderWidth = 2;
@@ -518,7 +523,8 @@
mComplicationRenderer.setComplicationData(
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText("Test text"))
- .build());
+ .build(),
+ true);
// WHEN the complication is drawn in ambient mode and as highlighted
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, true, false, false, true);
RectF bounds = new RectF(mComplicationBounds);
@@ -538,7 +544,8 @@
mComplicationRenderer.setComplicationData(
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText("Test text"))
- .build());
+ .build(),
+ true);
// WHEN the complication is drawn in low bit ambient mode
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, true, true, false, false);
assertThat(mComplicationRenderer.mAmbientPaintSet.mPrimaryTextPaint.isAntiAlias())
@@ -558,7 +565,8 @@
mComplicationRenderer.setComplicationData(
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText("Test text"))
- .build());
+ .build(),
+ true);
// WHEN the complication is drawn in low bit ambient mode
mComplicationRenderer.draw(mMockCanvas, REFERENCE_TIME, true, false, false, false);
// THEN paints in ambient paint set has anti alias enabled
@@ -644,7 +652,8 @@
.setLongText(ComplicationText.plainText("Foo"))
.setLongTitle(ComplicationText.plainText("Bar"))
.setIcon(mMockIcon)
- .build());
+ .build(),
+ true);
ComplicationStyle newStyle = new ComplicationStyle();
newStyle.setBorderRadius(radius);
mComplicationRenderer.updateStyle(newStyle, new ComplicationStyle());
@@ -671,7 +680,8 @@
.setLongText(ComplicationText.plainText("Foo"))
.setSmallImage(mMockSmallImage)
.setBurnInProtectionSmallImage(mMockBurnInProtectionSmallImage)
- .build());
+ .build(),
+ true);
Drawable smallImage = loadIconFromMock(mMockSmallImage);
Drawable burnInProtectionSmallImage = loadIconFromMock(mMockBurnInProtectionSmallImage);
@@ -698,7 +708,8 @@
new ComplicationData.Builder(TYPE_LONG_TEXT)
.setLongText(ComplicationText.plainText("Foo"))
.setSmallImage(mMockSmallImage)
- .build());
+ .build(),
+ true);
loadIconFromMock(mMockSmallImage);
@@ -717,7 +728,8 @@
.setBurnInProtectionIcon(mMockBurnInProtectionIcon)
.setSmallImage(mMockSmallImage)
.setBurnInProtectionSmallImage(mMockBurnInProtectionSmallImage)
- .build());
+ .build(),
+ true);
Drawable loadedIcon = loadIconFromMock(mMockIcon);
Drawable loadedBurnInProtectionIcon = loadIconFromMock(mMockBurnInProtectionIcon);
@@ -742,7 +754,8 @@
.setRangedMinValue(1)
.setRangedValue(5)
.setRangedMaxValue(10)
- .build());
+ .build(),
+ true);
mComplicationRenderer.setRangedValueProgressHidden(true);
ComplicationRenderer anotherRenderer =
@@ -751,7 +764,8 @@
new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText("foo"))
.setShortTitle(ComplicationText.plainText("bar"))
- .build());
+ .build(),
+ true);
assertThat(anotherRenderer.hasSameLayout(mComplicationRenderer)).isTrue();
}
@@ -764,13 +778,14 @@
.setRangedMinValue(1)
.setRangedValue(5)
.setRangedMaxValue(10)
- .build());
+ .build(),
+ true);
mComplicationRenderer.setRangedValueProgressHidden(true);
ComplicationRenderer anotherRenderer =
createRendererWithBounds(mComplicationRenderer.getBounds());
anotherRenderer.setComplicationData(
- new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build());
+ new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build(), true);
assertThat(anotherRenderer.hasSameLayout(mComplicationRenderer)).isTrue();
}
@@ -778,7 +793,7 @@
@Test
public void invalidateCalledWhenIconLoaded() {
mComplicationRenderer.setComplicationData(
- new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build());
+ new ComplicationData.Builder(TYPE_ICON).setIcon(mMockIcon).build(), true);
loadIconFromMock(mMockIcon);
@@ -790,7 +805,8 @@
mComplicationRenderer.setComplicationData(
new ComplicationData.Builder(TYPE_SMALL_IMAGE)
.setSmallImage(mMockSmallImage)
- .build());
+ .build(),
+ true);
loadIconFromMock(mMockSmallImage);
@@ -803,7 +819,8 @@
new ComplicationData.Builder(TYPE_ICON)
.setIcon(mMockIcon)
.setBurnInProtectionIcon(mMockBurnInProtectionIcon)
- .build());
+ .build(),
+ true);
loadIconFromMock(mMockBurnInProtectionIcon);
diff --git a/wear/wear-watchface/api/current.txt b/wear/wear-watchface/api/current.txt
index 4fb343b..37fb061 100644
--- a/wear/wear-watchface/api/current.txt
+++ b/wear/wear-watchface/api/current.txt
@@ -17,16 +17,17 @@
ctor public CanvasComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable, androidx.wear.watchface.WatchState watchState);
method public void drawOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, @ColorInt int color);
method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
- method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
+ method @UiThread public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
method @UiThread public boolean isHighlighted();
method public void onAttach(androidx.wear.watchface.Complication complication);
method public void onDetach();
method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
method public final void setDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable value);
method @UiThread public void setIdAndData(androidx.wear.complications.data.IdAndComplicationData? value);
+ method public void setIdComplicationDataSync(androidx.wear.complications.data.IdAndComplicationData? idAndComplicationData);
method @UiThread public void setIsHighlighted(boolean value);
property public final androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable;
- property public androidx.wear.complications.data.IdAndComplicationData? idAndData;
+ property @UiThread public androidx.wear.complications.data.IdAndComplicationData? idAndData;
property @UiThread public boolean isHighlighted;
}
diff --git a/wear/wear-watchface/api/public_plus_experimental_current.txt b/wear/wear-watchface/api/public_plus_experimental_current.txt
index 4fb343b..37fb061 100644
--- a/wear/wear-watchface/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface/api/public_plus_experimental_current.txt
@@ -17,16 +17,17 @@
ctor public CanvasComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable, androidx.wear.watchface.WatchState watchState);
method public void drawOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, @ColorInt int color);
method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
- method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
+ method @UiThread public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
method @UiThread public boolean isHighlighted();
method public void onAttach(androidx.wear.watchface.Complication complication);
method public void onDetach();
method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
method public final void setDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable value);
method @UiThread public void setIdAndData(androidx.wear.complications.data.IdAndComplicationData? value);
+ method public void setIdComplicationDataSync(androidx.wear.complications.data.IdAndComplicationData? idAndComplicationData);
method @UiThread public void setIsHighlighted(boolean value);
property public final androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable;
- property public androidx.wear.complications.data.IdAndComplicationData? idAndData;
+ property @UiThread public androidx.wear.complications.data.IdAndComplicationData? idAndData;
property @UiThread public boolean isHighlighted;
}
diff --git a/wear/wear-watchface/api/restricted_current.txt b/wear/wear-watchface/api/restricted_current.txt
index 3ea9707..a4ad2dc 100644
--- a/wear/wear-watchface/api/restricted_current.txt
+++ b/wear/wear-watchface/api/restricted_current.txt
@@ -17,16 +17,17 @@
ctor public CanvasComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable, androidx.wear.watchface.WatchState watchState);
method public void drawOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, @ColorInt int color);
method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
- method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
+ method @UiThread public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
method @UiThread public boolean isHighlighted();
method public void onAttach(androidx.wear.watchface.Complication complication);
method public void onDetach();
method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
method public final void setDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable value);
method @UiThread public void setIdAndData(androidx.wear.complications.data.IdAndComplicationData? value);
+ method public void setIdComplicationDataSync(androidx.wear.complications.data.IdAndComplicationData? idAndComplicationData);
method @UiThread public void setIsHighlighted(boolean value);
property public final androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable;
- property public androidx.wear.complications.data.IdAndComplicationData? idAndData;
+ property @UiThread public androidx.wear.complications.data.IdAndComplicationData? idAndData;
property @UiThread public boolean isHighlighted;
}
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
index 50128ac..2074339 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
@@ -82,6 +82,9 @@
/** The [IdAndComplicationData] to render. */
public var idAndData: IdAndComplicationData?
+
+ /** @hide */
+ public fun setIdComplicationDataSync(idAndComplicationData: IdAndComplicationData?)
}
/**
@@ -205,13 +208,28 @@
drawable.isHighlighted = value
}
+ private var _idAndData: IdAndComplicationData? = null
+
/** The [IdAndComplicationData] to use when rendering the complication. */
- override var idAndData: IdAndComplicationData? = null
+ override var idAndData: IdAndComplicationData?
+ @UiThread
+ get() = _idAndData
@UiThread
set(value) {
- drawable.complicationData = value?.complicationData?.asWireComplicationData()
- field = value
+ drawable.setComplicationData(
+ value?.complicationData?.asWireComplicationData(),
+ true
+ )
+ _idAndData = value
}
+
+ override fun setIdComplicationDataSync(idAndComplicationData: IdAndComplicationData?) {
+ _idAndData = idAndComplicationData
+ drawable.setComplicationData(
+ idAndComplicationData?.complicationData?.asWireComplicationData(),
+ false
+ )
+ }
}
/**
@@ -274,6 +292,7 @@
ComplicationBoundsType.ROUND_RECT,
complicationBounds
)
+
/**
* Constructs a [Builder] for a complication with bound type
* [ComplicationBoundsType.BACKGROUND] whose bounds cover the entire screen. A background
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index da8e3e7..c1334b1 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -499,14 +499,17 @@
}
val oldComplicationData =
- watchFaceImpl.complicationsManager.complications.mapValues {
- it.value.renderer.idAndData?.complicationData ?: NoDataComplicationData()
+ watchFaceImpl.complicationsManager.complications.values.map {
+ it.renderer.idAndData ?: IdAndComplicationData(it.id, NoDataComplicationData())
}
params.idAndComplicationDatumWireFormats?.let {
for (idAndData in it) {
- watchFaceImpl.onComplicationDataUpdate(
- idAndData.id, idAndData.complicationData.asApiComplicationData()
- )
+ watchFaceImpl.complicationsManager[idAndData.id]!!.renderer
+ .setIdComplicationDataSync(
+ IdAndComplicationData(
+ idAndData.id, idAndData.complicationData.asApiComplicationData()
+ )
+ )
}
}
@@ -523,8 +526,9 @@
}
if (params.idAndComplicationDatumWireFormats != null) {
- for ((id, data) in oldComplicationData) {
- watchFaceImpl.onComplicationDataUpdate(id, data)
+ for (idAndData in oldComplicationData) {
+ watchFaceImpl.complicationsManager[idAndData.complicationId]!!.renderer
+ .setIdComplicationDataSync(idAndData)
}
}
@@ -560,11 +564,12 @@
var screenshotComplicationData = params.complicationData
if (screenshotComplicationData != null) {
prevIdAndComplicationData = it.renderer.idAndData
- it.renderer.idAndData =
+ it.renderer.setIdComplicationDataSync(
IdAndComplicationData(
params.complicationId,
screenshotComplicationData
)
+ )
}
it.renderer.render(
@@ -576,7 +581,7 @@
// Restore previous ComplicationData & style if required.
if (params.complicationData != null) {
- it.renderer.idAndData = prevIdAndComplicationData
+ it.renderer.setIdComplicationDataSync(prevIdAndComplicationData)
}
if (newStyle != null) {
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
index 76bbf74..7074358 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -149,6 +149,7 @@
TAG,
String.format("Didn't find WorkSpec for id %s", mWorkSpecId));
resolve(false);
+ mWorkDatabase.setTransactionSuccessful();
return;
}
@@ -191,6 +192,7 @@
// This is not a problem for JobScheduler because we will only reschedule
// work if JobScheduler is unaware of a jobId.
resolve(true);
+ mWorkDatabase.setTransactionSuccessful();
return;
}
}