Merge "Supporting ImmutableMap in Room." into androidx-main
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index d676337..8095df7 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -1441,18 +1441,6 @@
}
}
- // Update the transparent snapshot if necessary
- // This doesn't need to take the sync because it is updating thread local state.
- (threadSnapshot.get() as? TransparentObserverMutableSnapshot)?.let {
- threadSnapshot.set(
- TransparentObserverMutableSnapshot(
- currentGlobalSnapshot.get(),
- it.specifiedReadObserver,
- it.specifiedWriteObserver
- )
- )
- it.dispose()
- }
return result
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
index 3448a42..aec9166 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
@@ -113,8 +113,10 @@
if (!isObserving) {
isObserving = true
try {
- applyMap.map.removeValueIf {
- it === scope
+ synchronized(applyMaps) {
+ applyMap.map.removeValueIf {
+ it === scope
+ }
}
Snapshot.observe(readObserver, null, block)
} finally {
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt
index 2d7e1ec..66b607b 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt
@@ -17,9 +17,13 @@
package androidx.compose.runtime.snapshots
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import kotlin.concurrent.thread
import kotlin.test.Test
import kotlin.test.assertEquals
+import kotlin.test.assertNull
class SnapshotStateObserverTests {
@@ -282,6 +286,44 @@
assertEquals(1, changes2)
}
+ @Test // regression test for 192677711
+ fun tryToReproduceRaceCondition() {
+ var running = true
+ var threadException: Exception? = null
+ try {
+ thread {
+ try {
+ while (running) {
+ Snapshot.sendApplyNotifications()
+ }
+ } catch (e: Exception) {
+ threadException = e
+ }
+ }
+
+ for (i in 1..10000) {
+ val state1 by mutableStateOf(0)
+ var state2 by mutableStateOf(true)
+ val observer = SnapshotStateObserver({}).apply {
+ start()
+ }
+ repeat(1000) {
+ observer.observeReads(Unit, {}) {
+ @Suppress("UNUSED_EXPRESSION")
+ state1
+ if (state2) {
+ state2 = false
+ }
+ }
+ }
+ assertNull(threadException)
+ }
+ } finally {
+ running = false
+ }
+ assertNull(threadException)
+ }
+
private fun runSimpleTest(
block: (modelObserver: SnapshotStateObserver, data: MutableState<Int>) -> Unit
) {
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
index 5ce7ca3..6ec7593 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
@@ -703,6 +703,27 @@
nestedChild.dispose()
}
+ @Test // Regression test for b/193006595
+ fun transparentSnapshotAdvancesCorrectly() {
+ val state = Snapshot.observe({}) {
+ // In a transparent snapshot, advance the global snapshot
+ Snapshot.notifyObjectsInitialized()
+
+ // Create an apply an object in a snapshot
+ val state = atomic {
+ mutableStateOf(0)
+ }
+
+ // Ensure that the object can be accessed in the observer
+ assertEquals(0, state.value)
+
+ state
+ }
+
+ // Ensure that the object can be accessed globally.
+ assertEquals(0, state.value)
+ }
+
private var count = 0
@BeforeTest
diff --git a/compose/ui/ui/src/androidAndroidTest/AndroidManifest.xml b/compose/ui/ui/src/androidAndroidTest/AndroidManifest.xml
index 5a120c7..d62742c 100644
--- a/compose/ui/ui/src/androidAndroidTest/AndroidManifest.xml
+++ b/compose/ui/ui/src/androidAndroidTest/AndroidManifest.xml
@@ -29,5 +29,8 @@
<activity android:name="androidx.fragment.app.FragmentActivity"/>
<activity android:name="androidx.compose.ui.window.ActivityWithFlagSecure"/>
<activity android:name="androidx.compose.ui.RecyclerViewActivity" />
+ <activity
+ android:name="androidx.compose.ui.draw.NotHardwareAcceleratedActivity"
+ android:hardwareAccelerated="false" />
</application>
</manifest>
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/NotHardwareAcceleratedActivityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/NotHardwareAcceleratedActivityTest.kt
new file mode 100644
index 0000000..7657dec2
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/NotHardwareAcceleratedActivityTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.draw
+
+import android.os.Build
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.GOLDEN_UI
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.background
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class NotHardwareAcceleratedActivityTest {
+
+ @get:Rule
+ val composeTestRule = createAndroidComposeRule<NotHardwareAcceleratedActivity>()
+
+ @get:Rule
+ val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_UI)
+
+ @Test
+ fun layerTransformationsApplied() {
+ composeTestRule.setContent {
+ Box(Modifier.size(200.dp).background(Color.White).testTag("box")) {
+ Box(
+ Modifier
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) {
+ val offset = 50.dp.roundToPx()
+ placeable.placeWithLayer(offset, offset) {
+ alpha = 0.5f
+ scaleX = 0.5f
+ scaleY = 0.5f
+ }
+ }
+ }
+ .size(100.dp)
+ .background(Color.Red)
+ )
+ }
+ }
+
+ composeTestRule.onNodeWithTag("box")
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, "not_hardware_accelerated_activity")
+ }
+}
+
+class NotHardwareAcceleratedActivity : ComponentActivity()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 748621d..ed7fb62 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -614,7 +614,10 @@
// We can't be confident that RenderNode is supported, so we try and fail over to
// the ViewLayer implementation. We'll try even on on P devices, but it will fail
// until ART allows things on the unsupported list on P.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isRenderNodeCompatible) {
+ if (isHardwareAccelerated &&
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+ isRenderNodeCompatible
+ ) {
try {
return RenderNodeLayer(
this,
diff --git a/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/lint/ApiLintVersionsTest.kt b/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/lint/ApiLintVersionsTest.kt
index 07406d5..a917fde5 100644
--- a/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/lint/ApiLintVersionsTest.kt
+++ b/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/lint/ApiLintVersionsTest.kt
@@ -35,6 +35,6 @@
Assert.assertEquals(CURRENT_API, registry.api)
// Intentionally fails in IDE, because we use different API version in
// studio and command line
- Assert.assertEquals(3, registry.minApi)
+ Assert.assertEquals(8, registry.minApi)
}
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt
index 7d79231..b6d549f 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt
@@ -16,6 +16,8 @@
package androidx.room.compiler.processing.ksp
+import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticFileMemberContainer
+import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
@@ -42,14 +44,33 @@
* Node that this is not necessarily the parent declaration. e.g. when a property is declared in
* a constructor, its containing type is actual two levels up.
*/
+@OptIn(KspExperimental::class)
internal fun KSDeclaration.findEnclosingMemberContainer(
env: KspProcessingEnv
): KspMemberContainer? {
- return findEnclosingAncestorClassDeclaration()?.let {
+ val memberContainer = findEnclosingAncestorClassDeclaration()?.let {
env.wrapClassDeclaration(it)
} ?: this.containingFile?.let {
env.wrapKSFile(it)
}
+ memberContainer?.let {
+ return it
+ }
+ // in compiled files, we may not find it. Try using the binary name
+
+ val ownerJvmClassName = when (this) {
+ is KSPropertyDeclaration -> env.resolver.getOwnerJvmClassName(this)
+ is KSFunctionDeclaration -> env.resolver.getOwnerJvmClassName(this)
+ else -> null
+ } ?: return null
+ // Binary name of a top level type is its canonical name. So we just load it directly by
+ // that value
+ env.findTypeElement(ownerJvmClassName)?.let {
+ return it
+ }
+ // When a top level function/property is compiled, its containing class does not exist in KSP,
+ // neither the file. So instead, we synthesize one
+ return KspSyntheticFileMemberContainer(ownerJvmClassName)
}
private fun KSDeclaration.findEnclosingAncestorClassDeclaration(): KSClassDeclaration? {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSFunctionExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSFunctionExt.kt
index 76e7323..1e646c6 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSFunctionExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSFunctionExt.kt
@@ -17,6 +17,7 @@
package androidx.room.compiler.processing.ksp
import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
@@ -43,8 +44,7 @@
else -> error(
"""
Unexpected overridee type for $this ($overridee).
- Please file a bug with steps to reproduce.
- https://issuetracker.google.com/issues/new?component=413107
+ Please file a bug at $ISSUE_TRACKER_LINK.
""".trimIndent()
)
} ?: returnType
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
index dd6e2bd..f8c7623 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
@@ -19,6 +19,7 @@
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.javac.kotlin.typeNameFromJvmSignature
import androidx.room.compiler.processing.tryBox
+import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSDeclaration
@@ -255,8 +256,8 @@
} catch (ex: NoSuchMethodException) {
throw IllegalStateException(
"""
- Room couldn't find the constructor it is looking for in JavaPoet. Please file a bug at
- https://issuetracker.google.com/issues/new?component=413107
+ Room couldn't find the constructor it is looking for in JavaPoet.
+ Please file a bug at $ISSUE_TRACKER_LINK.
""".trimIndent(),
ex
)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
index cb4ee5f..f2a52bb 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
@@ -21,6 +21,7 @@
import androidx.room.compiler.processing.XExecutableParameterElement
import androidx.room.compiler.processing.XHasModifiers
import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE
+import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
import com.google.devtools.ksp.isConstructor
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.Modifier
@@ -76,8 +77,10 @@
val enclosingContainer = declaration.findEnclosingMemberContainer(env)
checkNotNull(enclosingContainer) {
- "XProcessing does not currently support annotations on top level " +
- "functions with KSP. Cannot process $declaration."
+ """
+ Couldn't find the container element for $declaration.
+ Please file a bug at $ISSUE_TRACKER_LINK.
+ """
}
return when {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
index ae3b1f7..e2a0758 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
@@ -18,6 +18,7 @@
import androidx.room.compiler.processing.XFiler
import androidx.room.compiler.processing.XMessager
+import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.symbol.KSFile
@@ -87,8 +88,8 @@
Diagnostic.Kind.WARNING,
"""
No dependencies are reported for $fileName which will prevent
- incremental compilation. Please file a bug at:
- https://issuetracker.google.com/issues/new?component=413107
+ incremental compilation.
+ Please file a bug at $ISSUE_TRACKER_LINK.
""".trimIndent()
)
Dependencies.ALL_FILES
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index 46889e0..1726c96 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -79,7 +79,7 @@
)
}
- override fun findTypeElement(qName: String): XTypeElement? {
+ override fun findTypeElement(qName: String): KspTypeElement? {
return typeElementStore[qName]
}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt
new file mode 100644
index 0000000..35dc043
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.ksp.synthetic
+
+import androidx.room.compiler.processing.XAnnotation
+import androidx.room.compiler.processing.XAnnotationBox
+import androidx.room.compiler.processing.XEquality
+import androidx.room.compiler.processing.ksp.KspMemberContainer
+import androidx.room.compiler.processing.ksp.KspType
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.squareup.javapoet.ClassName
+import kotlin.reflect.KClass
+
+/**
+ * When a top level function/member is compiled, the generated Java class does not exist in KSP.
+ *
+ * This wrapper synthesizes one from the JVM binary name
+ *
+ * https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.1
+ */
+internal class KspSyntheticFileMemberContainer(
+ private val binaryName: String
+) : KspMemberContainer, XEquality {
+ override val equalityItems: Array<out Any?> by lazy {
+ arrayOf(binaryName)
+ }
+
+ override val type: KspType?
+ get() = null
+
+ override val declaration: KSDeclaration?
+ get() = null
+
+ override val className: ClassName by lazy {
+ val packageName = binaryName.substringBeforeLast(
+ delimiter = '.',
+ missingDelimiterValue = ""
+ )
+ val shortNames = if (packageName == "") {
+ binaryName
+ } else {
+ binaryName.substring(packageName.length + 1)
+ }.split('$')
+ ClassName.get(
+ packageName,
+ shortNames.first(),
+ *shortNames.drop(1).toTypedArray()
+ )
+ }
+
+ override fun kindName(): String {
+ return "synthethic top level file"
+ }
+
+ override val fallbackLocationText: String
+ get() = binaryName
+
+ override val docComment: String?
+ get() = null
+
+ override fun <T : Annotation> getAnnotations(annotation: KClass<T>): List<XAnnotationBox<T>> {
+ return emptyList()
+ }
+
+ override fun getAllAnnotations(): List<XAnnotation> {
+ return emptyList()
+ }
+
+ override fun hasAnnotation(annotation: KClass<out Annotation>): Boolean {
+ return false
+ }
+
+ override fun hasAnnotationWithPackage(pkg: String): Boolean {
+ return false
+ }
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/util/ErrorMessages.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/util/ErrorMessages.kt
new file mode 100644
index 0000000..1386a08
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/util/ErrorMessages.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.util
+
+/**
+ * Link to the issue tracker to report bugs.
+ */
+internal val ISSUE_TRACKER_LINK = "https://issuetracker.google.com/issues/new?component=413107"
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/TopLevelMembersTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/TopLevelMembersTest.kt
index 5582ac2..4054f04 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/TopLevelMembersTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/TopLevelMembersTest.kt
@@ -23,7 +23,7 @@
import androidx.room.compiler.processing.util.kspProcessingEnv
import androidx.room.compiler.processing.util.kspResolver
import androidx.room.compiler.processing.util.runKspTest
-import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
@@ -61,34 +61,37 @@
sources = listOf(appSrc),
classpath = classpath
) { invocation ->
- // b/188822146
- // TODO add lib package here once Room updates to a version that includes the
- // https://github.com/google/ksp/issues/396 fix (1.5.0-1.0.0-alpha09)
- val declarations = invocation.kspResolver.getDeclarationsFromPackage("app")
- declarations.filterIsInstance<KSFunctionDeclaration>()
- .toList().let { methods ->
- assertThat(methods).hasSize(1)
- methods.forEach { method ->
- val element = KspExecutableElement.create(
- env = invocation.kspProcessingEnv,
- declaration = method
- )
- assertThat(element.containing.isTypeElement()).isFalse()
- assertThat(element.isStatic()).isTrue()
+ listOf("lib", "app").forEach { pkg ->
+ val declarations = invocation.kspResolver.getDeclarationsFromPackage(pkg)
+ declarations.filterIsInstance<KSFunctionDeclaration>()
+ .toList().let { methods ->
+ assertWithMessage(pkg).that(methods).hasSize(1)
+ methods.forEach { method ->
+ val element = KspExecutableElement.create(
+ env = invocation.kspProcessingEnv,
+ declaration = method
+ )
+ assertWithMessage(pkg).that(
+ element.containing.isTypeElement()
+ ).isFalse()
+ assertWithMessage(pkg).that(element.isStatic()).isTrue()
+ }
}
- }
- declarations.filterIsInstance<KSPropertyDeclaration>()
- .toList().let { properties ->
- assertThat(properties).hasSize(2)
- properties.forEach {
- val element = KspFieldElement.create(
- env = invocation.kspProcessingEnv,
- declaration = it
- )
- assertThat(element.containing.isTypeElement()).isFalse()
- assertThat(element.isStatic()).isTrue()
+ declarations.filterIsInstance<KSPropertyDeclaration>()
+ .toList().let { properties ->
+ assertWithMessage(pkg).that(properties).hasSize(2)
+ properties.forEach {
+ val element = KspFieldElement.create(
+ env = invocation.kspProcessingEnv,
+ declaration = it
+ )
+ assertWithMessage(pkg).that(
+ element.containing.isTypeElement()
+ ).isFalse()
+ assertWithMessage(pkg).that(element.isStatic()).isTrue()
+ }
}
- }
+ }
}
}
}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt
new file mode 100644
index 0000000..1ef355a
--- /dev/null
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.ksp.synthetic
+
+import androidx.room.compiler.processing.ksp.KspFieldElement
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.getField
+import androidx.room.compiler.processing.util.kspResolver
+import androidx.room.compiler.processing.util.runKspTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import com.google.devtools.ksp.KspExperimental
+import com.google.devtools.ksp.symbol.KSPropertyDeclaration
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@OptIn(KspExperimental::class)
+class KspSyntheticFileMemberContainerTest {
+ @Test
+ fun topLevel_noPackage() {
+ val annotation = Source.kotlin(
+ "MyAnnotation.kt",
+ """
+ annotation class MyAnnotation
+ """.trimIndent()
+ )
+ val appSrc = Source.kotlin(
+ "App.kt",
+ """
+ @MyAnnotation
+ val appMember = 1
+ """.trimIndent()
+ )
+ runKspTest(
+ sources = listOf(annotation, appSrc)
+ ) { invocation ->
+ val elements = invocation.kspResolver.getSymbolsWithAnnotation("MyAnnotation").toList()
+ assertThat(elements).hasSize(1)
+ val className = elements.map {
+ val owner = invocation.kspResolver.getOwnerJvmClassName(it as KSPropertyDeclaration)
+ assertWithMessage(it.toString()).that(owner).isNotNull()
+ KspSyntheticFileMemberContainer(owner!!).className
+ }.first()
+ assertThat(className.packageName()).isEmpty()
+ assertThat(className.simpleNames()).containsExactly("AppKt")
+ }
+ }
+
+ @Test
+ fun nestedClassNames() {
+ fun buildSources(pkg: String) = listOf(
+ Source.java(
+ "$pkg.JavaClass",
+ """
+ package $pkg;
+ public class JavaClass {
+ int member;
+ public static class NestedClass {
+ int member;
+ }
+ public class InnerClass {
+ int member;
+ }
+ }
+ """.trimIndent()
+ ),
+ Source.java(
+ "${pkg}JavaClass",
+ """
+ public class ${pkg}JavaClass {
+ int member;
+ public static class NestedClass {
+ int member;
+ }
+ public class InnerClass {
+ int member;
+ }
+ }
+ """.trimIndent()
+ ),
+ Source.kotlin(
+ "$pkg/KotlinClass.kt",
+ """
+ package $pkg
+ class KotlinClass {
+ val member = 1
+ class NestedClass {
+ val member = 1
+ }
+ inner class InnerClass {
+ val member = 1
+ }
+ }
+ """.trimIndent()
+ ),
+ Source.kotlin(
+ "KotlinClass.kt",
+ """
+ class ${pkg}KotlinClass {
+ val member = 1
+ class NestedClass {
+ val member = 1
+ }
+ inner class InnerClass {
+ val member = 1
+ }
+ }
+ """.trimIndent()
+ )
+ )
+ val lib = compileFiles(buildSources("lib"))
+ runKspTest(
+ sources = buildSources("app"),
+ classpath = lib
+ ) { invocation ->
+ fun runTest(qName: String) {
+ invocation.processingEnv.requireTypeElement(qName).let { target ->
+ val field = target.getField("member") as KspFieldElement
+ val owner = invocation.kspResolver.getOwnerJvmClassName(field.declaration)
+ assertWithMessage(qName).that(owner).isNotNull()
+ val synthetic = KspSyntheticFileMemberContainer(owner!!)
+ assertWithMessage(qName).that(target.className).isEqualTo(synthetic.className)
+ }
+ }
+ listOf("lib", "app").forEach { pkg ->
+ // test both top level and in package cases
+ listOf(pkg, "$pkg.").forEach { prefix ->
+ runTest("${prefix}JavaClass")
+ runTest("${prefix}JavaClass.NestedClass")
+ runTest("${prefix}JavaClass.InnerClass")
+ runTest("${prefix}KotlinClass")
+ runTest("${prefix}KotlinClass.NestedClass")
+ runTest("${prefix}KotlinClass.InnerClass")
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/CardDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/CardDemo.kt
new file mode 100644
index 0000000..a480cb27
--- /dev/null
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/CardDemo.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.integration.demos
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.AppCard
+import androidx.wear.compose.material.Card
+import androidx.wear.compose.material.CardDefaults
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Text
+import androidx.wear.compose.material.TitleCard
+
+@Composable
+fun CardDemo() {
+ Column(
+ modifier = Modifier.fillMaxSize()
+ .padding(start = 8.dp, end = 8.dp)
+ .verticalScroll(
+ rememberScrollState()
+ ),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(modifier = Modifier.size(30.dp))
+ Card(
+ onClick = {},
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Text("Basic unopinionated chip")
+ Text("Sets the shape")
+ Text("and the background")
+ }
+ }
+ Spacer(modifier = Modifier.size(4.dp))
+ AppCard(
+ onClick = {},
+ appName = { Text("AppName") },
+ title = { Text("AppCard") },
+ time = { Text("now") },
+ body = {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Text("Some body content")
+ Text("and some more body content")
+ }
+ },
+ )
+ Spacer(modifier = Modifier.size(4.dp))
+ TitleCard(
+ onClick = {},
+ title = { Text("TitleCard") },
+ time = { Text("now") },
+ body = {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Text("Some body content")
+ Text("and some more body content")
+ }
+ },
+ )
+ Spacer(modifier = Modifier.size(4.dp))
+ TitleCard(
+ onClick = {},
+ title = { Text("TitleCard") },
+ body = {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Text("This title card doesn't show time")
+ }
+ }
+ )
+ Spacer(modifier = Modifier.size(4.dp))
+ TitleCard(
+ onClick = {},
+ title = { Text("Custom TitleCard") },
+ body = {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Text("This title card emphasises the title with custom color")
+ }
+ },
+ titleColor = Color.Yellow
+ )
+ Spacer(modifier = Modifier.size(4.dp))
+ TitleCard(
+ onClick = {},
+ title = {
+ Column {
+ Text("Custom TitleCard")
+ Text("With a Coloured Secondary Label", color = Color.Yellow)
+ }
+ },
+ body = {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Text("This title card emphasises the title with custom color")
+ }
+ },
+ )
+ Spacer(modifier = Modifier.size(4.dp))
+ TitleCard(
+ onClick = {},
+ title = { Text("TitleCard With an ImageBackground") },
+ body = {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Text("Text coloured to stand out on the image")
+ }
+ },
+ backgroundPainter = CardDefaults.imageBackgroundPainter(
+ backgroundImagePainter = painterResource(id = R.drawable.backgroundimage1)
+ ),
+ bodyColor = MaterialTheme.colors.onSurface,
+ titleColor = MaterialTheme.colors.onSurface,
+ )
+ Spacer(modifier = Modifier.size(30.dp))
+ }
+}
\ No newline at end of file
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
index 80814b4..3dc7725 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
@@ -30,5 +30,6 @@
)
),
ComposableDemo("Toggle Button") { ToggleButtons() },
+ ComposableDemo("Card") { CardDemo() },
),
)
diff --git a/wear/compose/integration-tests/demos/src/main/res/drawable/backgroundimage1.png b/wear/compose/integration-tests/demos/src/main/res/drawable/backgroundimage1.png
new file mode 100644
index 0000000..fbb9332
--- /dev/null
+++ b/wear/compose/integration-tests/demos/src/main/res/drawable/backgroundimage1.png
Binary files differ
diff --git a/wear/compose/material/api/current.txt b/wear/compose/material/api/current.txt
index b7a3b71..5f663c0 100644
--- a/wear/compose/material/api/current.txt
+++ b/wear/compose/material/api/current.txt
@@ -33,14 +33,16 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter cardBackgroundPainter(optional long startBackgroundColor, optional long endBackgroundColor, optional androidx.compose.ui.unit.LayoutDirection gradientDirection);
method public float getAppImageSize();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+ method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter imageBackgroundPainter(androidx.compose.ui.graphics.painter.Painter backgroundImagePainter, optional androidx.compose.ui.graphics.Brush backgroundImageScrimBrush);
property public final float AppImageSize;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
field public static final androidx.wear.compose.material.CardDefaults INSTANCE;
}
public final class CardKt {
- method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
+ method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
method @androidx.compose.runtime.Composable public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long contentColor, optional boolean enabled, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void TitleCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? time, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long titleColor, optional long timeColor, optional long bodyColor);
}
@androidx.compose.runtime.Stable public interface ChipColors {
diff --git a/wear/compose/material/api/public_plus_experimental_current.txt b/wear/compose/material/api/public_plus_experimental_current.txt
index b7a3b71..5f663c0 100644
--- a/wear/compose/material/api/public_plus_experimental_current.txt
+++ b/wear/compose/material/api/public_plus_experimental_current.txt
@@ -33,14 +33,16 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter cardBackgroundPainter(optional long startBackgroundColor, optional long endBackgroundColor, optional androidx.compose.ui.unit.LayoutDirection gradientDirection);
method public float getAppImageSize();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+ method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter imageBackgroundPainter(androidx.compose.ui.graphics.painter.Painter backgroundImagePainter, optional androidx.compose.ui.graphics.Brush backgroundImageScrimBrush);
property public final float AppImageSize;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
field public static final androidx.wear.compose.material.CardDefaults INSTANCE;
}
public final class CardKt {
- method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
+ method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
method @androidx.compose.runtime.Composable public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long contentColor, optional boolean enabled, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void TitleCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? time, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long titleColor, optional long timeColor, optional long bodyColor);
}
@androidx.compose.runtime.Stable public interface ChipColors {
diff --git a/wear/compose/material/api/restricted_current.txt b/wear/compose/material/api/restricted_current.txt
index b7a3b71..5f663c0 100644
--- a/wear/compose/material/api/restricted_current.txt
+++ b/wear/compose/material/api/restricted_current.txt
@@ -33,14 +33,16 @@
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter cardBackgroundPainter(optional long startBackgroundColor, optional long endBackgroundColor, optional androidx.compose.ui.unit.LayoutDirection gradientDirection);
method public float getAppImageSize();
method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+ method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.painter.Painter imageBackgroundPainter(androidx.compose.ui.graphics.painter.Painter backgroundImagePainter, optional androidx.compose.ui.graphics.Brush backgroundImageScrimBrush);
property public final float AppImageSize;
property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
field public static final androidx.wear.compose.material.CardDefaults INSTANCE;
}
public final class CardKt {
- method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
+ method @androidx.compose.runtime.Composable public static void AppCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> appName, kotlin.jvm.functions.Function0<kotlin.Unit> time, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? appImage, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long appColor, optional long timeColor, optional long titleColor, optional long bodyColor);
method @androidx.compose.runtime.Composable public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long contentColor, optional boolean enabled, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.runtime.Composable public static void TitleCard(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> body, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? time, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long titleColor, optional long timeColor, optional long bodyColor);
}
@androidx.compose.runtime.Stable public interface ChipColors {
diff --git a/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/CardTest.kt b/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/CardTest.kt
index ef3a6ee1..674c6da 100644
--- a/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/CardTest.kt
+++ b/wear/compose/material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/CardTest.kt
@@ -226,14 +226,82 @@
) { MaterialTheme.colors.onSurfaceVariant2 }
@Test
- public fun app_chip_gives_default_colors(): Unit =
- verifyAppCardColors(
- { MaterialTheme.colors.primary },
- { MaterialTheme.colors.primary },
- { MaterialTheme.colors.onSurfaceVariant },
- { MaterialTheme.colors.onSurface },
- { MaterialTheme.colors.onSurfaceVariant2 },
- )
+ public fun app_card_gives_default_colors() {
+ var expectedAppImageColor = Color.Transparent
+ var expectedAppColor = Color.Transparent
+ var expectedTimeColor = Color.Transparent
+ var expectedTitleColor = Color.Transparent
+ var expectedBodyColor = Color.Transparent
+ var actualBodyColor = Color.Transparent
+ var actualTitleColor = Color.Transparent
+ var actualTimeColor = Color.Transparent
+ var actualAppColor = Color.Transparent
+ var actualAppImageColor = Color.Transparent
+ val testBackground = Color.White
+
+ rule.setContentWithTheme {
+ expectedAppImageColor = MaterialTheme.colors.primary
+ expectedAppColor = MaterialTheme.colors.primary
+ expectedTimeColor = MaterialTheme.colors.onSurfaceVariant
+ expectedTitleColor = MaterialTheme.colors.onSurface
+ expectedBodyColor = MaterialTheme.colors.onSurfaceVariant2
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(testBackground)
+ ) {
+ AppCard(
+ onClick = {},
+ appName = { actualAppColor = LocalContentColor.current },
+ appImage = { actualAppImageColor = LocalContentColor.current },
+ time = { actualTimeColor = LocalContentColor.current },
+ body = { actualBodyColor = LocalContentColor.current },
+ title = { actualTitleColor = LocalContentColor.current },
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+ }
+
+ assertEquals(expectedAppImageColor, actualAppImageColor)
+ assertEquals(expectedAppColor, actualAppColor)
+ assertEquals(expectedTimeColor, actualTimeColor)
+ assertEquals(expectedTitleColor, actualTitleColor)
+ assertEquals(expectedBodyColor, actualBodyColor)
+ }
+
+ @Test
+ public fun title_card_gives_default_colors() {
+ var expectedTimeColor = Color.Transparent
+ var expectedTitleColor = Color.Transparent
+ var expectedBodyColor = Color.Transparent
+ var actualBodyColor = Color.Transparent
+ var actualTitleColor = Color.Transparent
+ var actualTimeColor = Color.Transparent
+ val testBackground = Color.White
+
+ rule.setContentWithTheme {
+ expectedTimeColor = MaterialTheme.colors.onSurfaceVariant
+ expectedTitleColor = MaterialTheme.colors.onSurface
+ expectedBodyColor = MaterialTheme.colors.onSurfaceVariant2
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(testBackground)
+ ) {
+ TitleCard(
+ onClick = {},
+ time = { actualTimeColor = LocalContentColor.current },
+ body = { actualBodyColor = LocalContentColor.current },
+ title = { actualTitleColor = LocalContentColor.current },
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+ }
+
+ assertEquals(expectedTimeColor, actualTimeColor)
+ assertEquals(expectedTitleColor, actualTitleColor)
+ assertEquals(expectedBodyColor, actualBodyColor)
+ }
private fun verifyColors(
status: CardStatus,
@@ -261,55 +329,6 @@
assertEquals(expectedContent, actualContent)
}
-
- private fun verifyAppCardColors(
- appImageColor: @Composable () -> Color,
- appColor: @Composable () -> Color,
- timeColor: @Composable () -> Color,
- titleColor: @Composable () -> Color,
- bodyColor: @Composable () -> Color,
- ) {
- var expectedAppImageColor = Color.Transparent
- var expectedAppColor = Color.Transparent
- var expectedTimeColor = Color.Transparent
- var expectedTitleColor = Color.Transparent
- var expectedBodyColor = Color.Transparent
- var actualBodyColor = Color.Transparent
- var actualTitleColor = Color.Transparent
- var actualTimeColor = Color.Transparent
- var actualAppColor = Color.Transparent
- var actualAppImageColor = Color.Transparent
- val testBackground = Color.White
-
- rule.setContentWithTheme {
- expectedAppImageColor = appImageColor()
- expectedAppColor = appColor()
- expectedTimeColor = timeColor()
- expectedTitleColor = titleColor()
- expectedBodyColor = bodyColor()
- Box(
- modifier = Modifier
- .fillMaxSize()
- .background(testBackground)
- ) {
- AppCard(
- onClick = {},
- appName = { actualAppColor = LocalContentColor.current },
- appImage = { actualAppImageColor = LocalContentColor.current },
- time = { actualTimeColor = LocalContentColor.current },
- body = { actualBodyColor = LocalContentColor.current },
- title = { actualTitleColor = LocalContentColor.current },
- modifier = Modifier.testTag(TEST_TAG)
- )
- }
- }
-
- assertEquals(expectedAppImageColor, actualAppImageColor)
- assertEquals(expectedAppColor, actualAppColor)
- assertEquals(expectedTimeColor, actualTimeColor)
- assertEquals(expectedTitleColor, actualTitleColor)
- assertEquals(expectedBodyColor, actualBodyColor)
- }
}
public class CardFontTest {
@@ -373,6 +392,39 @@
assertEquals(expectedTitleTextStyle, actualTitleTextStyle)
assertEquals(expectedBodyTextStyle, actualBodyTextStyle)
}
+
+ @Test
+ public fun title_card_gives_correct_text_style_base() {
+ var actualTimeTextStyle = TextStyle.Default
+ var actualTitleTextStyle = TextStyle.Default
+ var actualBodyTextStyle = TextStyle.Default
+ var expectedTimeTextStyle = TextStyle.Default
+ var expectedTitleTextStyle = TextStyle.Default
+ var expectedBodyTextStyle = TextStyle.Default
+
+ rule.setContentWithTheme {
+ expectedTimeTextStyle = MaterialTheme.typography.caption1
+ expectedTitleTextStyle = MaterialTheme.typography.button
+ expectedBodyTextStyle = MaterialTheme.typography.body1
+
+ TitleCard(
+ onClick = {},
+ time = {
+ actualTimeTextStyle = LocalTextStyle.current
+ },
+ title = {
+ actualTitleTextStyle = LocalTextStyle.current
+ },
+ body = {
+ actualBodyTextStyle = LocalTextStyle.current
+ },
+ modifier = Modifier.testTag(TEST_TAG)
+ )
+ }
+ assertEquals(expectedTimeTextStyle, actualTimeTextStyle)
+ assertEquals(expectedTitleTextStyle, actualTitleTextStyle)
+ assertEquals(expectedBodyTextStyle, actualBodyTextStyle)
+ }
}
private fun ComposeContentTestRule.verifyHeight(expected: Dp, content: @Composable () -> Unit) {
diff --git a/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt b/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt
index 566c192..d1d93f4 100644
--- a/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt
+++ b/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt
@@ -40,6 +40,7 @@
import androidx.compose.ui.draw.paint
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.Shader
@@ -48,6 +49,7 @@
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
@@ -110,6 +112,7 @@
.matchParentSize()
.paint(
painter = backgroundPainter,
+ contentScale = ContentScale.FillBounds
)
val contentBoxModifier = Modifier
@@ -140,7 +143,10 @@
/**
* Opinionated Wear Material [Card] that offers a specific 5 slot layout to show information about
- * an application, e.g. a notification.
+ * an application, e.g. a notification. AppCards are designed to show interactive elements from
+ * multiple applications. They will typically be used by the system UI, e.g. for showing a list of
+ * notifications from different applications. However it could also be adapted by individual
+ * application developers to show information about different parts of their application.
*
* The first row of the layout has three slots, 1) a small optional application [Image] or [Icon] of
* size [CardDefaults.AppImageSize]x[CardDefaults.AppImageSize] dp, 2) an application name
@@ -154,13 +160,13 @@
* The rest of the [Card] contains the body content which can be either [Text] or an [Image].
*
* @param onClick Will be called when the user clicks the card
- * @param modifier Modifier to be applied to the card
* @param appName A slot for displaying the application name, expected to be a single line of text
* of [Typography.title3]
* @param time A slot for displaying the time relevant to the contents of the card, expected to be a
- * short piece of right aligned text.
+ * short piece of end aligned text.
* @param body A slot for displaying the details of the [Card], expected to be either [Text]
* (single or multiple-line) or an [Image]
+ * @param modifier Modifier to be applied to the card
* @param appImage A slot for a small ([CardDefaults.AppImageSize]x[CardDefaults.AppImageSize] )
* [Image] or [Icon] associated with the application.
* @param backgroundPainter A painter used to paint the background of the card. A card will
@@ -175,11 +181,11 @@
@Composable
public fun AppCard(
onClick: () -> Unit,
- modifier: Modifier = Modifier,
appName: @Composable () -> Unit,
time: @Composable () -> Unit,
title: @Composable () -> Unit,
body: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
appImage: @Composable (() -> Unit)? = null,
backgroundPainter: Painter = CardDefaults.cardBackgroundPainter(),
appColor: Color = MaterialTheme.colors.primary,
@@ -233,6 +239,88 @@
}
/**
+ * Opinionated Wear Material [Card] that offers a specific 3 slot layout to show interactive
+ * information about an application, e.g. a message. TitleCards are designed for use within an
+ * application.
+ *
+ * The first row of the layout has two slots. 1. a start aligned title (emphasised with the
+ * [titleColor] and expected to be start aligned text). The title text is expected to be a maximum
+ * of 2 lines of text. 2. An optional time that the application activity has occurred shown at the
+ * end of the row, expected to be an end aligned [Text] composable showing a time relevant to the
+ * contents of the [Card].
+ *
+ * The rest of the [Card] contains the body content which is expected to be [Text] or a contained
+ * [Image].
+ *
+ * Overall the [title] and [body] text should be no more than 5 rows of text combined.
+ *
+ * @param onClick Will be called when the user clicks the card
+ * @param title A slot for displaying the title of the card, expected to be one or two lines of text
+ * of [Typography.button]
+ * @param body A slot for displaying the details of the [Card], expected to be either [Text]
+ * (single or multiple-line) or an [Image]. If [Text] then it is expected to be a maximum of 4 lines
+ * of text of [Typography.body1]
+ * @param modifier Modifier to be applied to the card
+ * @param time An optional slot for displaying the time relevant to the contents of the card,
+ * expected to be a short piece of end aligned text.
+ * @param backgroundPainter A painter used to paint the background of the card. A title card can
+ * have either a gradient background or an image background, use
+ * [CardDefaults.cardBackgroundPainter()] or [CardDefaults.imageBackgroundPainter()] to obtain an
+ * appropriate painter
+ * @param titleColor The default color to use for title() slot unless explicitly set.
+ * @param timeColor The default color to use for time() slot unless explicitly set.
+ * @param bodyColor The default color to use for body() slot unless explicitly set.
+ */
+@Composable
+public fun TitleCard(
+ onClick: () -> Unit,
+ title: @Composable () -> Unit,
+ body: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ time: @Composable (() -> Unit)? = null,
+ backgroundPainter: Painter = CardDefaults.cardBackgroundPainter(),
+ titleColor: Color = MaterialTheme.colors.onSurface,
+ timeColor: Color = MaterialTheme.colors.onSurfaceVariant,
+ bodyColor: Color = MaterialTheme.colors.onSurfaceVariant2,
+) {
+ Card(
+ onClick = onClick,
+ modifier = modifier,
+ backgroundPainter = backgroundPainter,
+ enabled = true,
+ ) {
+ Column {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides titleColor,
+ LocalTextStyle provides MaterialTheme.typography.button,
+ content = title
+ )
+ if (time != null) {
+ Spacer(modifier = Modifier.width(4.dp))
+ Box(modifier = Modifier.weight(1.0f), contentAlignment = Alignment.CenterEnd) {
+ CompositionLocalProvider(
+ LocalContentColor provides timeColor,
+ LocalTextStyle provides MaterialTheme.typography.caption1,
+ content = time
+ )
+ }
+ }
+ }
+ Spacer(modifier = Modifier.height(8.dp))
+ CompositionLocalProvider(
+ LocalContentColor provides bodyColor,
+ LocalTextStyle provides MaterialTheme.typography.body1,
+ content = body
+ )
+ }
+ }
+}
+
+/**
* Contains the default values used by [Card]
*/
public object CardDefaults {
@@ -274,6 +362,35 @@
return BrushPainter(FortyFiveDegreeLinearGradient(backgroundColors))
}
+ /**
+ * Creates a [Painter] for the background of a [Card] that displays an Image with a scrim over
+ * the image to make sure that any content above the background will be legible.
+ *
+ * An Image background is a means to reinforce the meaning of information in a Card, e.g. To
+ * help to contextualize the information in a TitleCard
+ *
+ * Cards should have a content color that contrasts with the background image and scrim
+ *
+ * @param backgroundImagePainter The [Painter] to use to draw the background of the [Card]
+ * @param backgroundImageScrimBrush The [Brush] to use to paint a scrim over the background
+ * image to ensure that any text drawn over the image is legible
+ */
+ @Composable
+ public fun imageBackgroundPainter(
+ backgroundImagePainter: Painter,
+ backgroundImageScrimBrush: Brush = Brush.linearGradient(
+ colors = listOf(
+ MaterialTheme.colors.surface.copy(alpha = 1.0f),
+ MaterialTheme.colors.surface.copy(alpha = 0f)
+ )
+ )
+ ): Painter {
+ return ImageWithScrimPainter(
+ imagePainter = backgroundImagePainter,
+ brush = backgroundImageScrimBrush
+ )
+ }
+
private val CardHorizontalPadding = 12.dp
private val CardVerticalPadding = 12.dp