Merge "Add test for seek to end of media item" into androidx-master-dev
diff --git a/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index 427c11a..7218e30 100644
--- a/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -28,6 +28,7 @@
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPom
import org.gradle.api.publish.maven.MavenPublication
+import org.gradle.api.publish.tasks.GenerateModuleMetadata
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.create
import java.io.File
@@ -49,6 +50,9 @@
if (extension.publish.shouldPublish() && component.isAndroidOrJavaReleaseComponent()) {
val androidxGroup = validateCoordinatesAndGetGroup(extension)
group = androidxGroup
+ tasks.withType(GenerateModuleMetadata::class.java) {
+ it.enabled = false
+ }
configure<PublishingExtension> {
repositories {
it.maven { repo ->
diff --git a/buildSrc/src/main/kotlin/androidx/build/TaskUpToDateValidator.kt b/buildSrc/src/main/kotlin/androidx/build/TaskUpToDateValidator.kt
index 30e3241..3624a89 100644
--- a/buildSrc/src/main/kotlin/androidx/build/TaskUpToDateValidator.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/TaskUpToDateValidator.kt
@@ -78,6 +78,7 @@
"partiallyDejetifyArchive",
"postInstrumentCode",
"publishBenchmarkPluginMarkerMavenPublicationToMavenRepository",
+ "publishKotlinMultiplatformPublicationToMavenRepository",
"publishMavenPublicationToMavenRepository",
"publishMetadataPublicationToMavenRepository",
"publishPluginMavenPublicationToMavenRepository",
diff --git a/buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt b/buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt
index ada5665..4adf272 100644
--- a/buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/doclava/DoclavaTask.kt
@@ -23,6 +23,7 @@
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.external.javadoc.CoreJavadocOptions
+import org.gradle.external.javadoc.StandardJavadocDocletOptions
import java.io.File
// external/doclava/src/com/google/doclava/Errors.java
@@ -145,7 +146,7 @@
* "Configures" this DoclavaTask with parameters that might not be at their final values
* until this task is run.
*/
- private fun configureDoclava() = (options as CoreJavadocOptions).apply {
+ private fun configureDoclava() = (options as StandardJavadocDocletOptions).apply {
docletpath = this@DoclavaTask.docletpath
@@ -181,6 +182,9 @@
}
// Always treat this as an Android docs task.
addBooleanOption("android", true)
+
+ // Doclava does not understand -notimestamp option that is default since Gradle 6.0
+ isNoTimestamp = false
}
fun coreJavadocOptions(configure: CoreJavadocOptions.() -> Unit) =
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
index de986d5..a5884bf 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
@@ -27,6 +27,8 @@
import org.gradle.api.Project
import org.gradle.api.artifacts.ResolveException
import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskCollection
import org.gradle.api.tasks.TaskContainer
@@ -215,14 +217,22 @@
// TODO(b/143243490): can this be made to run more quickly, cleanly and clearly via some other
// approach, such as maybe with an ArtifactTransform?
open class LocateJarsTask : DefaultTask() {
+ init {
+ // This task does not correctly model inputs and outputs.
+ // Mark it to be never up to date
+ outputs.upToDateWhen { false }
+ }
+
// dependencies to search for .jar files
+ @get:Input
val inputDependencies = mutableListOf<String>()
// .jar files found in any dependencies
+ @get:OutputFiles
val outputJars = mutableListOf<File>()
@TaskAction
- fun Extract() {
+ fun extract() {
// setup
val inputDependencies = checkNotNull(inputDependencies) { "inputDependencies not set" }
val project = this.project
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
index 921e399..6aa1992 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
@@ -73,10 +73,12 @@
* {@link FocusMeteringAction} are executed in a row, only the latest one will work and
* other actions will be cancelled.
*
- * <p>If the {@link FocusMeteringAction} specifies more AF/AE/AWB regions than what is
- * supported on current device, only the first region and then in order up to the number of
- * regions supported by the device will be enabled. If it turns out no added regions can be
- * supported on the device, the returned {@link ListenableFuture} in
+ * <p>If the {@link FocusMeteringAction} specifies more AF/AE/AWB points than what is
+ * supported on current device, only the first point and then in order up to the number of
+ * points supported by the device will be enabled.
+ *
+ * <p>If none of the points with either AF/AE/AWB can be supported on the device,
+ * the returned {@link ListenableFuture} in
* {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} will fail immediately.
*
* @see FocusMeteringAction
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java b/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
index 95502661..697ae89 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
@@ -175,11 +175,19 @@
* Adds another {@link MeteringPoint} with default mode {@link MeteringMode#AF} |
* {@link MeteringMode#AE} | {@link MeteringMode#AWB}.
*
+ * <p>The points added here will be appended in order after the point set in
+ * {@link FocusMeteringAction.Builder#from(MeteringPoint)} or
+ * {@link FocusMeteringAction.Builder#from(MeteringPoint, int)}.
+ *
* <p>If more points are added than what current device supports for AF/AE/AWB, only the
- * first region and then in order up to the number of regions supported by the device
- * will be enabled. If it turns out no added points can be supported on the device, the
- * returned {@link ListenableFuture} in
- * {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} will fail immediately.
+ * first point and then in order up to the number of points supported on the device
+ * will be enabled.
+ *
+ * <p>If none of the points is supported on the device, this
+ * {@link FocusMeteringAction} will cause
+ * {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} to fail.
+ *
+ * @see CameraControl#startFocusAndMetering(FocusMeteringAction)
*/
@NonNull
public Builder addPoint(@NonNull MeteringPoint point) {
@@ -189,14 +197,19 @@
/**
* Adds another {@link MeteringPoint} with specified {@link MeteringMode}.
*
- * <p>If more points are added than what current device supports for AF/AE/AWB, only the
- * first region and then in order up to the number of regions supported by the device
- * will be enabled. If it turns out no added points can be supported on the device, the
- * returned {@link ListenableFuture} in
- * {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} will fail immediately.
+ * <p>The points added here will be appended in order after the point set in
+ * {@link FocusMeteringAction.Builder#from(MeteringPoint)} or
+ * {@link FocusMeteringAction.Builder#from(MeteringPoint, int)}.
*
- * @param mode Must be a valid {@link MeteringMode}, otherwise an
- * {@link IllegalArgumentException} is thrown.
+ * <p>If more points are added than what current device supports for AF/AE/AWB, only the
+ * first point and then in order up to the number of points supported on the device
+ * will be enabled.
+ *
+ * <p>If none of the points is supported on the device, this
+ * {@link FocusMeteringAction} will cause
+ * {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} to fail.
+ *
+ * @see CameraControl#startFocusAndMetering(FocusMeteringAction)
*/
@NonNull
public Builder addPoint(@NonNull MeteringPoint point, @MeteringMode int mode) {
diff --git a/development/apilint.py b/development/apilint.py
index ad3d8d5..e523d26 100755
--- a/development/apilint.py
+++ b/development/apilint.py
@@ -19,19 +19,28 @@
"""Script that will remind developers to run updateApi."""
import argparse
+import os.path
import sys
-WARNING_COLOR = '\033[93m'
+
+WARNING_COLOR = '\033[33m'
END_COLOR = '\033[0m'
-WARNING = """
+WARNING_NO_API_FILES = """
{}**********************************************************************
You changed library classes, but you have no current.txt changes.
Did you forget to run ./gradlew updateApi?
**********************************************************************{}
""".format(WARNING_COLOR, END_COLOR)
+WARNING_OLD_API_FILES = """
+{}**********************************************************************
+Your current.txt is older than your current changes in library classes.
+Did you forget to re-run ./gradlew updateApi?
+**********************************************************************{}
+""".format(WARNING_COLOR, END_COLOR)
+
def main(args=None):
parser = argparse.ArgumentParser()
@@ -40,16 +49,24 @@
args = parser.parse_args()
api_files = [f for f in args.file
if f.endswith('.txt') and '/api/' in f]
- if len(api_files) > 0:
+ source_files = [f for f in args.file
+ if (not "buildSrc/" in f and
+ "/src/main/" in f or
+ "/src/commonMain/" in f or
+ "/src/androidMain/" in f)]
+ if len(source_files) == 0:
sys.exit(0)
- for f in args.file:
- if (not "buildSrc/" in f and
- "/src/main/" in f or
- "/src/commonMain/" in f or
- "/src/androidMain/" in f):
- print(WARNING)
- sys.exit(77) # 77 is a warning code in repohooks
+ if len(api_files) == 0:
+ print(WARNING_NO_API_FILES)
+ sys.exit(77) # 77 is a warning code in repohooks
+
+ last_source_timestamp = max([os.path.getmtime(f) for f in source_files])
+ last_api_timestamp = max([os.path.getmtime(f) for f in api_files])
+
+ if last_source_timestamp > last_api_timestamp:
+ print(WARNING_OLD_API_FILES)
+ sys.exit(77) # 77 is a warning code in repohooks
sys.exit(0)
if __name__ == '__main__':
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index cf3bb85..9817e9f 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -5,4 +5,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-5.6.2-bin.zip
+distributionUrl=../../../../tools/external/gradle/gradle-6.0-bin.zip
diff --git a/lifecycle/integration-tests/incrementality/build.gradle b/lifecycle/integration-tests/incrementality/build.gradle
index 8207fb4..f036777 100644
--- a/lifecycle/integration-tests/incrementality/build.gradle
+++ b/lifecycle/integration-tests/incrementality/build.gradle
@@ -66,4 +66,5 @@
// lifecycle-common and annotation are the dependencies of lifecycle-compiler
tasks.findByPath("test").dependsOn(tasks.findByPath(":lifecycle:lifecycle-compiler:publish"),
tasks.findByPath(":lifecycle:lifecycle-common:publish"),
+ tasks.findByPath(":lifecycle:lifecycle-runtime:publish"),
tasks.findByPath(":annotation:annotation:publish"))
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/LaunchWhenTest.kt b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/LaunchWhenTest.kt
index b2dc7b3..7bdc5b8 100644
--- a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/LaunchWhenTest.kt
+++ b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/LaunchWhenTest.kt
@@ -17,13 +17,13 @@
package androidx.lifecycle
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
+import androidx.test.filters.MediumTest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
-@SmallTest
+@MediumTest
@RunWith(AndroidJUnit4::class)
class LaunchWhenTest {
private val expectations = Expectations()
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java
index c648c91..f074256 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/VideoView_WithPlayerTest.java
@@ -370,7 +370,7 @@
withAspectRatio(videoSizeFor1stItem.getWidth(), videoSizeFor1stItem.getHeight())));
// seekTo instead of skipToNextItem (b/144876689)
- playerWrapper.seekTo(Long.MAX_VALUE);
+ playerWrapper.seekTo(playerWrapper.getDurationMs());
assertTrue(latchFor2ndItem.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
onView(instanceOf(VideoSurfaceView.class)).check(matches(
withAspectRatio(videoSizeFor2ndItem.getWidth(), videoSizeFor2ndItem.getHeight())));
diff --git a/ui/gradle/wrapper/gradle-wrapper.properties b/ui/gradle/wrapper/gradle-wrapper.properties
index c62ad8a..ec9defd 100644
--- a/ui/gradle/wrapper/gradle-wrapper.properties
+++ b/ui/gradle/wrapper/gradle-wrapper.properties
@@ -5,4 +5,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=../../../../../tools/external/gradle/gradle-5.6.2-bin.zip
+distributionUrl=../../../../../tools/external/gradle/gradle-6.0-bin.zip
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RadioGroupBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RadioGroupBenchmark.kt
new file mode 100644
index 0000000..36ed034
--- /dev/null
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/benchmark/test/RadioGroupBenchmark.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2019 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.ui.benchmark.test
+
+import androidx.compose.Composable
+import androidx.compose.Model
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.ui.benchmark.ComposeBenchmarkRule
+import androidx.ui.benchmark.benchmarkFirstCompose
+import androidx.ui.benchmark.benchmarkFirstDraw
+import androidx.ui.benchmark.benchmarkFirstLayout
+import androidx.ui.benchmark.benchmarkFirstMeasure
+import androidx.ui.benchmark.toggleStateBenchmarkDraw
+import androidx.ui.benchmark.toggleStateBenchmarkLayout
+import androidx.ui.benchmark.toggleStateBenchmarkMeasure
+import androidx.ui.benchmark.toggleStateBenchmarkRecompose
+import androidx.ui.layout.Column
+import androidx.ui.material.MaterialTheme
+import androidx.ui.material.RadioGroup
+import androidx.ui.test.ComposeTestCase
+import androidx.ui.test.ToggleableTestCase
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Benchmark for [RadioGroup].
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class RadioGroupBenchmark {
+
+ @get:Rule
+ val benchmarkRule = ComposeBenchmarkRule()
+
+ private val radioCaseFactory = { RadioGroupTestCase() }
+
+ @Test
+ fun first_compose() {
+ benchmarkRule.benchmarkFirstCompose(radioCaseFactory)
+ }
+
+ @Test
+ fun first_measure() {
+ benchmarkRule.benchmarkFirstMeasure(radioCaseFactory)
+ }
+
+ @Test
+ fun first_layout() {
+ benchmarkRule.benchmarkFirstLayout(radioCaseFactory)
+ }
+
+ @Test
+ fun first_draw() {
+ benchmarkRule.benchmarkFirstDraw(radioCaseFactory)
+ }
+
+ @Test
+ fun toggleRadio_recompose() {
+ benchmarkRule.toggleStateBenchmarkRecompose(radioCaseFactory)
+ }
+
+ @Test
+ fun toggleRadio_measure() {
+ benchmarkRule.toggleStateBenchmarkMeasure(radioCaseFactory)
+ }
+
+ @Test
+ fun toggleRadio_layout() {
+ benchmarkRule.toggleStateBenchmarkLayout(radioCaseFactory)
+ }
+
+ @Test
+ fun toggleRadio_draw() {
+ benchmarkRule.toggleStateBenchmarkDraw(radioCaseFactory)
+ }
+}
+
+@Model
+internal class RadioGroupSelectedState<T>(var selected: T)
+
+internal class RadioGroupTestCase : ComposeTestCase, ToggleableTestCase {
+
+ private val radiosCount = 10
+ private val options = (0 until radiosCount).toList()
+ private val select = RadioGroupSelectedState(0)
+
+ override fun toggleState() {
+ select.selected = (select.selected + 1) % radiosCount
+ }
+
+ @Composable
+ override fun emitContent() {
+ MaterialTheme {
+ RadioGroup {
+ Column {
+ options.forEach { item ->
+ RadioGroupTextItem(
+ text = item.toString(),
+ selected = (select.selected == item),
+ onSelect = { select.selected = item })
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/core/WithConstraintsBenchmark.kt b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/core/WithConstraintsBenchmark.kt
index af313d2..c84a1c9 100644
--- a/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/core/WithConstraintsBenchmark.kt
+++ b/ui/integration-tests/benchmark/src/androidTest/java/androidx/ui/core/WithConstraintsBenchmark.kt
@@ -26,6 +26,7 @@
import androidx.ui.benchmark.toggleStateBenchmarkMeasureLayout
import androidx.ui.core.Placeable.PlacementScope.place
import androidx.ui.layout.Container
+import androidx.ui.layout.Size
import androidx.ui.layout.Spacer
import androidx.ui.test.ComposeTestCase
import androidx.ui.test.ToggleableTestCase
@@ -66,7 +67,7 @@
val size = +state { 200.dp }
this.state = size
Container(width = 300.dp, height = 300.dp) {
- Spacer(androidx.ui.layout.Size(width = size.value, height = size.value))
+ Spacer(Size(width = size.value, height = size.value))
}
}
@@ -85,7 +86,7 @@
this.state = size
WithConstraints {
Container(width = 300.dp, height = 300.dp) {
- Spacer(androidx.ui.layout.Size(width = size.value, height = size.value))
+ Spacer(Size(width = size.value, height = size.value))
}
}
}
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt
index 907e572..8745eee 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ColorPaletteTestCase.kt
@@ -24,6 +24,7 @@
import androidx.ui.graphics.Color
import androidx.ui.material.ColorPalette
import androidx.ui.material.MaterialTheme
+import androidx.ui.material.lightColorPalette
import androidx.ui.test.ComposeTestCase
import androidx.ui.test.ToggleableTestCase
@@ -65,7 +66,7 @@
*/
class ObservableColorPaletteTestCase : ColorPaletteTestCase() {
override fun createPalette(primary: Color): ColorPalette {
- return ColorPalette(primary = primary)
+ return lightColorPalette(primary = primary)
}
}
@@ -89,6 +90,7 @@
override val onBackground = Color.Black
override val onSurface = Color.Black
override val onError = Color.Black
+ override val isLight = true
}
}
diff --git a/ui/ui-core/api/0.1.0-dev03.txt b/ui/ui-core/api/0.1.0-dev03.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/0.1.0-dev03.txt
+++ b/ui/ui-core/api/0.1.0-dev03.txt
@@ -5,6 +5,7 @@
ctor public MathHelpersKt();
method public static float lerp(float start, float stop, float fraction);
method public static int lerp(int start, int stop, float fraction);
+ method public static long lerp(long start, long stop, float fraction);
method public static String toHexString(int);
method public static String toStringAsFixed(float, int digits);
}
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -5,6 +5,7 @@
ctor public MathHelpersKt();
method public static float lerp(float start, float stop, float fraction);
method public static int lerp(int start, int stop, float fraction);
+ method public static long lerp(long start, long stop, float fraction);
method public static String toHexString(int);
method public static String toStringAsFixed(float, int digits);
}
diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt
+++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt
@@ -5,6 +5,7 @@
ctor public MathHelpersKt();
method public static float lerp(float start, float stop, float fraction);
method public static int lerp(int start, int stop, float fraction);
+ method public static long lerp(long start, long stop, float fraction);
method public static String toHexString(int);
method public static String toStringAsFixed(float, int digits);
}
diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-core/api/public_plus_experimental_current.txt
@@ -5,6 +5,7 @@
ctor public MathHelpersKt();
method public static float lerp(float start, float stop, float fraction);
method public static int lerp(int start, int stop, float fraction);
+ method public static long lerp(long start, long stop, float fraction);
method public static String toHexString(int);
method public static String toStringAsFixed(float, int digits);
}
diff --git a/ui/ui-core/api/restricted_0.1.0-dev03.txt b/ui/ui-core/api/restricted_0.1.0-dev03.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/restricted_0.1.0-dev03.txt
+++ b/ui/ui-core/api/restricted_0.1.0-dev03.txt
@@ -5,6 +5,7 @@
ctor public MathHelpersKt();
method public static float lerp(float start, float stop, float fraction);
method public static int lerp(int start, int stop, float fraction);
+ method public static long lerp(long start, long stop, float fraction);
method public static String toHexString(int);
method public static String toStringAsFixed(float, int digits);
}
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index 481f79e..a2bf190 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -5,6 +5,7 @@
ctor public MathHelpersKt();
method public static float lerp(float start, float stop, float fraction);
method public static int lerp(int start, int stop, float fraction);
+ method public static long lerp(long start, long stop, float fraction);
method public static String toHexString(int);
method public static String toStringAsFixed(float, int digits);
}
diff --git a/ui/ui-core/src/main/java/androidx/ui/MathHelpers.kt b/ui/ui-core/src/main/java/androidx/ui/MathHelpers.kt
index 7b233d5..e707f3d 100644
--- a/ui/ui-core/src/main/java/androidx/ui/MathHelpers.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/MathHelpers.kt
@@ -16,19 +16,27 @@
package androidx.ui
import kotlin.math.roundToInt
+import kotlin.math.roundToLong
/**
* Linearly interpolate between [start] and [stop] with [fraction] fraction between them.
*/
fun lerp(start: Float, stop: Float, fraction: Float): Float {
- return start + (stop - start) * fraction
+ return (1 - fraction) * start + fraction * stop
}
/**
* Linearly interpolate between [start] and [stop] with [fraction] fraction between them.
*/
fun lerp(start: Int, stop: Int, fraction: Float): Int {
- return start + ((stop - start) * fraction).roundToInt()
+ return start + ((stop - start) * fraction.toDouble()).roundToInt()
+}
+
+/**
+ * Linearly interpolate between [start] and [stop] with [fraction] fraction between them.
+ */
+fun lerp(start: Long, stop: Long, fraction: Float): Long {
+ return start + ((stop - start) * fraction.toDouble()).roundToLong()
}
fun Float.toStringAsFixed(digits: Int) = String.format("%.${digits}f", this)
diff --git a/ui/ui-core/src/test/java/androidx/ui/MathHelpersTest.kt b/ui/ui-core/src/test/java/androidx/ui/MathHelpersTest.kt
new file mode 100644
index 0000000..598938d
--- /dev/null
+++ b/ui/ui-core/src/test/java/androidx/ui/MathHelpersTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2019 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.ui
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MathHelpersTest {
+
+ // `f = 16777216f` is the first value where `f + 1 == f` due to float imprecision, so that's
+ // where testing floating point errors becomes interesting
+ val testStart = 16777216L
+ val testEnd = testStart + 1000
+
+ @Test
+ fun testLerpLargeFloats() {
+ val from = 1f
+ for (x in testStart until testEnd) {
+ val to = x.toFloat()
+ assertThat(lerp(from, to, 0f)).isEqualTo(from)
+ assertThat(lerp(from, to, 1f)).isEqualTo(to)
+ }
+ }
+
+ @Test
+ fun testLerpLargeInts() {
+ val from = 1
+ for (x in testStart until testEnd) {
+ val to = x.toInt()
+ assertThat(lerp(from, to, 0f)).isEqualTo(from)
+ assertThat(lerp(from, to, 1f)).isEqualTo(to)
+ }
+ }
+
+ @Test
+ fun testLerpLargeLongs() {
+ val from = 1L
+ for (x in testStart until testEnd) {
+ val to = x.toLong()
+ assertThat(lerp(from, to, 0f)).isEqualTo(from)
+ assertThat(lerp(from, to, 1f)).isEqualTo(to)
+ }
+ }
+
+ @Test
+ fun testLerpSimpleFloats() {
+ val from = 0f
+ for (multiplier in 1..1000) {
+ val to = (4 * multiplier).toFloat()
+ assertThat(lerp(from, to, 0.00f)).isEqualTo((0 * multiplier).toFloat())
+ assertThat(lerp(from, to, 0.25f)).isEqualTo((1 * multiplier).toFloat())
+ assertThat(lerp(from, to, 0.50f)).isEqualTo((2 * multiplier).toFloat())
+ assertThat(lerp(from, to, 0.75f)).isEqualTo((3 * multiplier).toFloat())
+ assertThat(lerp(from, to, 1.00f)).isEqualTo((4 * multiplier).toFloat())
+ }
+ }
+
+ @Test
+ fun testLerpSimpleInts() {
+ val from = 0
+ for (multiplier in 1..1000) {
+ val to = (4 * multiplier).toInt()
+ assertThat(lerp(from, to, 0.00f)).isEqualTo((0 * multiplier).toInt())
+ assertThat(lerp(from, to, 0.25f)).isEqualTo((1 * multiplier).toInt())
+ assertThat(lerp(from, to, 0.50f)).isEqualTo((2 * multiplier).toInt())
+ assertThat(lerp(from, to, 0.75f)).isEqualTo((3 * multiplier).toInt())
+ assertThat(lerp(from, to, 1.00f)).isEqualTo((4 * multiplier).toInt())
+ }
+ }
+
+ @Test
+ fun testLerpSimpleLongs() {
+ val from = 0L
+ for (multiplier in 1..1000) {
+ val to = (4 * multiplier).toLong()
+ assertThat(lerp(from, to, 0.00f)).isEqualTo((0 * multiplier).toLong())
+ assertThat(lerp(from, to, 0.25f)).isEqualTo((1 * multiplier).toLong())
+ assertThat(lerp(from, to, 0.50f)).isEqualTo((2 * multiplier).toLong())
+ assertThat(lerp(from, to, 0.75f)).isEqualTo((3 * multiplier).toLong())
+ assertThat(lerp(from, to, 1.00f)).isEqualTo((4 * multiplier).toLong())
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
index 7f90d37..50e6c3a 100644
--- a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
@@ -16,24 +16,33 @@
package androidx.ui.foundation
import android.graphics.Bitmap
-import android.os.Build
import android.os.Handler
+import android.os.Looper
import android.view.PixelCopy
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
+import androidx.annotation.RequiresApi
import androidx.compose.Composable
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.ui.core.Alignment
import androidx.ui.core.AndroidComposeView
+import androidx.ui.core.Dp
import androidx.ui.core.Draw
import androidx.ui.core.IntPx
+import androidx.ui.core.Px
+import androidx.ui.core.TestTag
+import androidx.ui.core.Text
+import androidx.ui.core.dp
import androidx.ui.core.ipx
import androidx.ui.core.px
import androidx.ui.core.setContent
+import androidx.ui.core.toPx
import androidx.ui.core.toRect
import androidx.ui.core.withDensity
+import androidx.ui.foundation.shape.DrawShape
+import androidx.ui.foundation.shape.RectangleShape
import androidx.ui.graphics.Color
import androidx.ui.graphics.Paint
import androidx.ui.graphics.PaintingStyle
@@ -43,18 +52,23 @@
import androidx.ui.layout.ConstrainedBox
import androidx.ui.layout.Container
import androidx.ui.layout.DpConstraints
-import androidx.ui.core.Text
-import androidx.ui.core.dp
-import androidx.ui.core.sp
-import androidx.ui.layout.Padding
import androidx.ui.layout.Row
+import androidx.ui.semantics.Semantics
+import androidx.ui.test.GestureScope
+import androidx.ui.test.SemanticsNodeInteraction
import androidx.ui.test.android.AndroidComposeTestRule
import androidx.ui.test.assertIsDisplayed
import androidx.ui.test.assertIsNotDisplayed
import androidx.ui.test.createComposeRule
+import androidx.ui.test.doGesture
import androidx.ui.test.doScrollTo
+import androidx.ui.test.findByTag
import androidx.ui.test.findByText
-import androidx.ui.text.TextStyle
+import androidx.ui.test.sendSwipeDown
+import androidx.ui.test.sendSwipeLeft
+import androidx.ui.test.sendSwipeRight
+import androidx.ui.test.sendSwipeUp
+import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -74,10 +88,14 @@
// TODO(malkov/pavlis) : some tests here require activity access as we need
// to take screen's bitmap, abstract it better
- val activity
+ private val activity
get() = (composeTestRule as AndroidComposeTestRule).activityTestRule.activity
- val colors = listOf(
+ private val defaultCrossAxisSize = 45.ipx
+ private val defaultMainAxisSize = 40.ipx
+ private val defaultCellSize = 5.ipx
+
+ private val colors = listOf(
Color(red = 0xFF, green = 0, blue = 0, alpha = 0xFF),
Color(red = 0xFF, green = 0xA5, blue = 0, alpha = 0xFF),
Color(red = 0xFF, green = 0xFF, blue = 0, alpha = 0xFF),
@@ -88,8 +106,8 @@
Color(red = 0xA5, green = 0, blue = 0xFF, alpha = 0xFF)
)
- var drawLatch = CountDownLatch(1)
- lateinit var handler: Handler
+ private var drawLatch = CountDownLatch(1)
+ private lateinit var handler: Handler
@Before
fun setupDrawLatch() {
@@ -99,15 +117,16 @@
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @SdkSuppress(minSdkVersion = 26)
@Test
fun verticalScroller_SmallContent() {
- composeVerticalScroller()
+ val height = 40.ipx
- validateVerticalScroller(0, 40)
+ composeVerticalScroller(height = height)
+
+ validateVerticalScroller(height = height)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@Test
fun verticalScroller_SmallContent_Unscrollable() {
val scrollerPosition = ScrollerPosition()
@@ -115,9 +134,8 @@
// latch to wait for a new max to come on layout
val newMaxLatch = CountDownLatch(1)
- composeVerticalScroller(
- scrollerPosition
- )
+ composeVerticalScroller(scrollerPosition)
+
val onGlobalLayout = object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
newMaxLatch.countDown()
@@ -133,22 +151,26 @@
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @SdkSuppress(minSdkVersion = 26)
@Test
fun verticalScroller_LargeContent_NoScroll() {
- composeVerticalScroller(height = 30.ipx)
+ val height = 30.ipx
- validateVerticalScroller(0, 30)
+ composeVerticalScroller(height = height)
+
+ validateVerticalScroller(height = height)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @SdkSuppress(minSdkVersion = 26)
@Test
fun verticalScroller_LargeContent_ScrollToEnd() {
val scrollerPosition = ScrollerPosition()
+ val height = 30.ipx
+ val scrollDistance = 10.ipx
- composeVerticalScroller(scrollerPosition, height = 30.ipx)
+ composeVerticalScroller(scrollerPosition, height = height)
- validateVerticalScroller(0, 30)
+ validateVerticalScroller(height = height)
// The 'draw' method will no longer be called because only the position
// changes during scrolling. Therefore, we should just wait until the draw stage
@@ -161,40 +183,47 @@
}
composeTestRule.runOnUiThread {
activity.window.decorView.viewTreeObserver.addOnDrawListener(onDrawListener)
- assertEquals(10.px, scrollerPosition.maxPosition)
- scrollerPosition.scrollTo(10.px)
+ assertEquals(scrollDistance.toPx(), scrollerPosition.maxPosition)
+ scrollerPosition.scrollTo(scrollDistance.toPx())
}
assertTrue(latch.await(1, TimeUnit.SECONDS))
composeTestRule.runOnUiThread {
activity.window.decorView.viewTreeObserver.removeOnDrawListener(onDrawListener)
}
- validateVerticalScroller(10, 30)
+ validateVerticalScroller(offset = scrollDistance, height = height)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @SdkSuppress(minSdkVersion = 26)
@Test
fun horizontalScroller_SmallContent() {
- composeHorizontalScroller()
+ val width = 40.ipx
- validateHorizontalScroller(0, 40)
+ composeHorizontalScroller(width = width)
+
+ validateHorizontalScroller(width = width)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @SdkSuppress(minSdkVersion = 26)
@Test
fun horizontalScroller_LargeContent_NoScroll() {
- composeHorizontalScroller(width = 30.ipx)
+ val width = 30.ipx
- validateHorizontalScroller(0, 30)
+ composeHorizontalScroller(width = width)
+
+ validateHorizontalScroller(width = width)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @SdkSuppress(minSdkVersion = 26)
@Test
fun horizontalScroller_LargeContent_ScrollToEnd() {
+ val width = 30.ipx
+ val scrollDistance = 10.ipx
+
val scrollerPosition = ScrollerPosition()
- composeHorizontalScroller(scrollerPosition, width = 30.ipx)
+ composeHorizontalScroller(scrollerPosition, width = width)
- validateHorizontalScroller(0, 30)
+ validateHorizontalScroller(width = width)
// The 'draw' method will no longer be called because only the position
// changes during scrolling. Therefore, we should just wait until the draw stage
@@ -207,14 +236,14 @@
}
composeTestRule.runOnUiThread {
activity.window.decorView.viewTreeObserver.addOnDrawListener(onDrawListener)
- assertEquals(10.px, scrollerPosition.maxPosition)
- scrollerPosition.scrollTo(10.px)
+ assertEquals(scrollDistance.toPx(), scrollerPosition.maxPosition)
+ scrollerPosition.scrollTo(scrollDistance.toPx())
}
assertTrue(latch.await(1, TimeUnit.SECONDS))
composeTestRule.runOnUiThread {
activity.window.decorView.viewTreeObserver.removeOnDrawListener(onDrawListener)
}
- validateHorizontalScroller(10, 30)
+ validateHorizontalScroller(offset = scrollDistance, width = width)
}
@Test
@@ -226,6 +255,7 @@
.doScrollTo()
.assertIsDisplayed()
}
+
@Test
fun horizontalScroller_scrollTo_scrollForward() {
createScrollableContent(isVertical = false)
@@ -250,6 +280,7 @@
.doScrollTo()
.assertIsDisplayed()
}
+
@Test
fun horizontalScroller_scrollTo_scrollBack() {
createScrollableContent(isVertical = false)
@@ -265,13 +296,60 @@
.assertIsDisplayed()
}
+ @Test
+ fun verticalScroller_swipeUp_swipeDown() {
+ swipeScrollerAndBack(true, GestureScope::sendSwipeUp, GestureScope::sendSwipeDown)
+ }
+
+ @Test
+ fun horizontalScroller_swipeLeft_swipeRight() {
+ swipeScrollerAndBack(false, GestureScope::sendSwipeLeft, GestureScope::sendSwipeRight)
+ }
+
+ private fun swipeScrollerAndBack(
+ isVertical: Boolean,
+ firstSwipe: GestureScope.() -> Unit,
+ secondSwipe: GestureScope.() -> Unit
+ ) {
+ val scrollerPosition = ScrollerPosition()
+
+ createScrollableContent(isVertical, scrollerPosition = scrollerPosition)
+ assertThat(scrollerPosition.getValueOnUiThread()).isEqualTo(0.px)
+
+ findByTag("scroller")
+ .doGesture { firstSwipe() }
+ .awaitScrollAnimation(scrollerPosition)
+
+ val scrolledValue = scrollerPosition.getValueOnUiThread()
+ assertThat(scrolledValue).isGreaterThan(0.px)
+
+ findByTag("scroller")
+ .doGesture { secondSwipe() }
+ .awaitScrollAnimation(scrollerPosition)
+
+ assertThat(scrollerPosition.getValueOnUiThread()).isLessThan(scrolledValue)
+ }
+
+ private fun ScrollerPosition.getValueOnUiThread(): Px {
+ var value = 0.px
+ val latch = CountDownLatch(1)
+ composeTestRule.runOnUiThread {
+ value = this.value
+ latch.countDown()
+ }
+ latch.await()
+ return value
+ }
+
private fun composeVerticalScroller(
scrollerPosition: ScrollerPosition = ScrollerPosition(),
- height: IntPx = 40.ipx
+ width: IntPx = defaultCrossAxisSize,
+ height: IntPx = defaultMainAxisSize,
+ rowHeight: IntPx = defaultCellSize
) {
// We assume that the height of the device is more than 45 px
withDensity(composeTestRule.density) {
- val constraints = DpConstraints.tightConstraints(45.px.toDp(), height.toDp())
+ val constraints = DpConstraints.tightConstraints(width.toDp(), height.toDp())
composeTestRule.runOnUiThread {
activity.setContent {
Align(alignment = Alignment.TopLeft) {
@@ -280,8 +358,8 @@
Column {
colors.forEach { color ->
Container(
- height = 5.px.toDp(),
- width = 45.px.toDp()
+ height = rowHeight.toDp(),
+ width = width.toDp()
) {
Draw { canvas, parentSize ->
val paint = Paint()
@@ -305,11 +383,13 @@
private fun composeHorizontalScroller(
scrollerPosition: ScrollerPosition = ScrollerPosition(),
- width: IntPx = 40.ipx
+ width: IntPx = defaultMainAxisSize,
+ height: IntPx = defaultCrossAxisSize,
+ columnWidth: IntPx = defaultCellSize
) {
// We assume that the height of the device is more than 45 px
withDensity(composeTestRule.density) {
- val constraints = DpConstraints.tightConstraints(width.toDp(), 45.px.toDp())
+ val constraints = DpConstraints.tightConstraints(width.toDp(), height.toDp())
composeTestRule.runOnUiThread {
activity.setContent {
Align(alignment = Alignment.TopLeft) {
@@ -318,8 +398,8 @@
Row {
colors.forEach { color ->
Container(
- width = 5.px.toDp(),
- height = 45.px.toDp()
+ width = columnWidth.toDp(),
+ height = height.toDp()
) {
Draw { canvas, parentSize ->
val paint = Paint()
@@ -341,21 +421,23 @@
}
}
+ @RequiresApi(api = 26)
private fun validateVerticalScroller(
- offset: Int,
- height: Int,
- width: Int = 45
+ offset: IntPx = 0.ipx,
+ width: IntPx = 45.ipx,
+ height: IntPx = 40.ipx,
+ rowHeight: IntPx = 5.ipx
) {
assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
val bitmap = waitAndScreenShot()
- assertTrue(bitmap.height >= height)
- assertTrue(bitmap.width >= 45)
- for (y in 0 until height) {
- val colorIndex = (offset + y) / 5
+ assertTrue(bitmap.height >= height.value)
+ assertTrue(bitmap.width >= width.value)
+ for (y in 0 until height.value) {
+ val colorIndex = (offset.value + y) / rowHeight.value
val expectedColor = colors[colorIndex]
- for (x in 0 until width) {
+ for (x in 0 until width.value) {
val pixel = bitmap.getPixel(x, y)
assertEquals(
"Expected $expectedColor, but got ${Color(pixel)} at $x, $y",
@@ -365,21 +447,23 @@
}
}
+ @RequiresApi(api = 26)
private fun validateHorizontalScroller(
- offset: Int,
- width: Int,
- height: Int = 45
+ offset: IntPx = 0.ipx,
+ width: IntPx = 40.ipx,
+ height: IntPx = 45.ipx,
+ columnWidth: IntPx = 5.ipx
) {
assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
val bitmap = waitAndScreenShot()
- assertTrue(bitmap.height >= 45)
- assertTrue(bitmap.width >= width)
- for (x in 0 until width) {
- val colorIndex = (offset + x) / 5
+ assertTrue(bitmap.height >= height.value)
+ assertTrue(bitmap.width >= width.value)
+ for (x in 0 until width.value) {
+ val colorIndex = (offset.value + x) / columnWidth.value
val expectedColor = colors[colorIndex]
- for (y in 0 until height) {
+ for (y in 0 until height.value) {
val pixel = bitmap.getPixel(x, y)
assertEquals(
"Expected $expectedColor, but got ${Color(pixel)} at $x, $y",
@@ -389,25 +473,37 @@
}
}
- private fun createScrollableContent(isVertical: Boolean) {
+ private fun createScrollableContent(
+ isVertical: Boolean,
+ itemCount: Int = 100,
+ width: Dp = 100.dp,
+ height: Dp = 100.dp,
+ scrollerPosition: ScrollerPosition = ScrollerPosition()
+ ) {
composeTestRule.setContent {
- val style = TextStyle(fontSize = 30.sp)
val content = @Composable {
- for (i in 1..100) {
- Text(text = i.toString(), style = style)
+ repeat(itemCount) {
+ Text(text = "$it")
}
}
- Padding(padding = 10.dp) {
- if (isVertical) {
- VerticalScroller {
- Column {
- content()
- }
- }
- } else {
- HorizontalScroller {
- Row {
- content()
+ Align(alignment = Alignment.TopLeft) {
+ Container(width = width, height = height) {
+ DrawShape(RectangleShape, Color.White)
+ TestTag("scroller") {
+ Semantics {
+ if (isVertical) {
+ VerticalScroller(scrollerPosition) {
+ Column {
+ content()
+ }
+ }
+ } else {
+ HorizontalScroller(scrollerPosition) {
+ Row {
+ content()
+ }
+ }
+ }
}
}
}
@@ -415,6 +511,7 @@
}
}
+ @RequiresApi(api = 26) // For PixelCopy.request(Window, Rect, Bitmap, listener, Handler)
private fun waitAndScreenShot(): Bitmap {
val view = findAndroidComposeView()
waitForDraw(view)
@@ -441,14 +538,35 @@
return dest
}
+ private fun SemanticsNodeInteraction.awaitScrollAnimation(
+ scroller: ScrollerPosition
+ ): SemanticsNodeInteraction {
+ if (!scroller.holder.animatedFloat.isRunning) {
+ return this
+ }
+ val latch = CountDownLatch(1)
+ val handler = Handler(Looper.getMainLooper())
+ handler.post(object : Runnable {
+ override fun run() {
+ if (scroller.holder.animatedFloat.isRunning) {
+ handler.post(this)
+ } else {
+ latch.countDown()
+ }
+ }
+ })
+ latch.await()
+ return this
+ }
+
// TODO(malkov): ALL below is copypaste from LayoutTest as this test in ui-foundation now
- internal fun findAndroidComposeView(): AndroidComposeView {
+ private fun findAndroidComposeView(): AndroidComposeView {
val contentViewGroup = activity.findViewById<ViewGroup>(android.R.id.content)
return findAndroidComposeView(contentViewGroup)!!
}
- internal fun findAndroidComposeView(parent: ViewGroup): AndroidComposeView? {
+ private fun findAndroidComposeView(parent: ViewGroup): AndroidComposeView? {
for (index in 0 until parent.childCount) {
val child = parent.getChildAt(index)
if (child is AndroidComposeView) {
@@ -463,7 +581,7 @@
return null
}
- internal fun waitForDraw(view: View) {
+ private fun waitForDraw(view: View) {
val viewDrawLatch = CountDownLatch(1)
val listener = object : ViewTreeObserver.OnDrawListener {
override fun onDraw() {
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
index 7881172..5bb083e 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
@@ -53,7 +53,7 @@
@Model
class ScrollerPosition(initial: Float = 0f) {
- internal val holder = AnimatedValueHolder(initial)
+ internal val holder = AnimatedValueHolder(-initial)
/**
* maxPosition this scroller that consume this ScrollerPosition can reach, or [Px.Infinity]
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeEditorModelTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeEditorModelTest.kt
index 98e68f7..58aec5d 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeEditorModelTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeEditorModelTest.kt
@@ -29,8 +29,9 @@
import androidx.ui.input.SetSelectionEditOp
import androidx.ui.input.TextInputService
import androidx.ui.test.createComposeRule
-import androidx.ui.test.doClick
+import androidx.ui.test.doGesture
import androidx.ui.test.findByTag
+import androidx.ui.test.sendClick
import androidx.ui.test.waitForIdleCompose
import androidx.ui.text.TextRange
import com.google.common.truth.Truth.assertThat
@@ -90,7 +91,7 @@
// Perform click to focus in.
val element = findByTag("textField")
- element.doClick()
+ element.doGesture { sendClick(1f, 1f) }
// Verify startInput is called and capture the callback.
val onEditCommandCaptor = argumentCaptor<(List<EditOperation>) -> Unit>()
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeFullEditorModelTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeFullEditorModelTest.kt
index c376455..bc9bb2f 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeFullEditorModelTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeFullEditorModelTest.kt
@@ -29,8 +29,9 @@
import androidx.ui.input.SetSelectionEditOp
import androidx.ui.input.TextInputService
import androidx.ui.test.createComposeRule
-import androidx.ui.test.doClick
+import androidx.ui.test.doGesture
import androidx.ui.test.findByTag
+import androidx.ui.test.sendClick
import androidx.ui.test.waitForIdleCompose
import androidx.ui.text.TextRange
import com.google.common.truth.Truth.assertThat
@@ -92,7 +93,7 @@
// Perform click to focus in.
val element = findByTag("textField")
- element.doClick()
+ element.doGesture { sendClick(1f, 1f) }
// Verify startInput is called and capture the callback.
val onEditCommandCaptor = argumentCaptor<(List<EditOperation>) -> Unit>()
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeStringTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeStringTest.kt
index 023c015..13e37b5 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeStringTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/TextFieldOnValueChangeStringTest.kt
@@ -29,8 +29,9 @@
import androidx.ui.input.SetSelectionEditOp
import androidx.ui.input.TextInputService
import androidx.ui.test.createComposeRule
-import androidx.ui.test.doClick
+import androidx.ui.test.doGesture
import androidx.ui.test.findByTag
+import androidx.ui.test.sendClick
import androidx.ui.test.waitForIdleCompose
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any
@@ -90,7 +91,7 @@
// Perform click to focus in.
val element = findByTag("textField")
- element.doClick()
+ element.doGesture { sendClick(1f, 1f) }
// Verify startInput is called and capture the callback.
val onEditCommandCaptor = argumentCaptor<(List<EditOperation>) -> Unit>()
diff --git a/ui/ui-layout/api/0.1.0-dev03.txt b/ui/ui-layout/api/0.1.0-dev03.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/0.1.0-dev03.txt
+++ b/ui/ui-layout/api/0.1.0-dev03.txt
@@ -7,6 +7,40 @@
method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class Aligned {
+ method public androidx.ui.core.LayoutModifier getBottom();
+ method public androidx.ui.core.LayoutModifier getBottomCenter();
+ method public androidx.ui.core.LayoutModifier getBottomLeft();
+ method public androidx.ui.core.LayoutModifier getBottomRight();
+ method public androidx.ui.core.LayoutModifier getCenter();
+ method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+ method public androidx.ui.core.LayoutModifier getCenterLeft();
+ method public androidx.ui.core.LayoutModifier getCenterRight();
+ method public androidx.ui.core.LayoutModifier getCenterVertically();
+ method public androidx.ui.core.LayoutModifier getEnd();
+ method public androidx.ui.core.LayoutModifier getStart();
+ method public androidx.ui.core.LayoutModifier getTop();
+ method public androidx.ui.core.LayoutModifier getTopCenter();
+ method public androidx.ui.core.LayoutModifier getTopLeft();
+ method public androidx.ui.core.LayoutModifier getTopRight();
+ property public final androidx.ui.core.LayoutModifier Bottom;
+ property public final androidx.ui.core.LayoutModifier BottomCenter;
+ property public final androidx.ui.core.LayoutModifier BottomLeft;
+ property public final androidx.ui.core.LayoutModifier BottomRight;
+ property public final androidx.ui.core.LayoutModifier Center;
+ property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+ property public final androidx.ui.core.LayoutModifier CenterLeft;
+ property public final androidx.ui.core.LayoutModifier CenterRight;
+ property public final androidx.ui.core.LayoutModifier CenterVertically;
+ property public final androidx.ui.core.LayoutModifier End;
+ property public final androidx.ui.core.LayoutModifier Start;
+ property public final androidx.ui.core.LayoutModifier Top;
+ property public final androidx.ui.core.LayoutModifier TopCenter;
+ property public final androidx.ui.core.LayoutModifier TopLeft;
+ property public final androidx.ui.core.LayoutModifier TopRight;
+ field public static final androidx.ui.layout.Aligned! INSTANCE;
+ }
+
public final class AlignmentLineKt {
ctor public AlignmentLineKt();
method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/api/current.txt b/ui/ui-layout/api/current.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/current.txt
+++ b/ui/ui-layout/api/current.txt
@@ -7,6 +7,40 @@
method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class Aligned {
+ method public androidx.ui.core.LayoutModifier getBottom();
+ method public androidx.ui.core.LayoutModifier getBottomCenter();
+ method public androidx.ui.core.LayoutModifier getBottomLeft();
+ method public androidx.ui.core.LayoutModifier getBottomRight();
+ method public androidx.ui.core.LayoutModifier getCenter();
+ method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+ method public androidx.ui.core.LayoutModifier getCenterLeft();
+ method public androidx.ui.core.LayoutModifier getCenterRight();
+ method public androidx.ui.core.LayoutModifier getCenterVertically();
+ method public androidx.ui.core.LayoutModifier getEnd();
+ method public androidx.ui.core.LayoutModifier getStart();
+ method public androidx.ui.core.LayoutModifier getTop();
+ method public androidx.ui.core.LayoutModifier getTopCenter();
+ method public androidx.ui.core.LayoutModifier getTopLeft();
+ method public androidx.ui.core.LayoutModifier getTopRight();
+ property public final androidx.ui.core.LayoutModifier Bottom;
+ property public final androidx.ui.core.LayoutModifier BottomCenter;
+ property public final androidx.ui.core.LayoutModifier BottomLeft;
+ property public final androidx.ui.core.LayoutModifier BottomRight;
+ property public final androidx.ui.core.LayoutModifier Center;
+ property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+ property public final androidx.ui.core.LayoutModifier CenterLeft;
+ property public final androidx.ui.core.LayoutModifier CenterRight;
+ property public final androidx.ui.core.LayoutModifier CenterVertically;
+ property public final androidx.ui.core.LayoutModifier End;
+ property public final androidx.ui.core.LayoutModifier Start;
+ property public final androidx.ui.core.LayoutModifier Top;
+ property public final androidx.ui.core.LayoutModifier TopCenter;
+ property public final androidx.ui.core.LayoutModifier TopLeft;
+ property public final androidx.ui.core.LayoutModifier TopRight;
+ field public static final androidx.ui.layout.Aligned! INSTANCE;
+ }
+
public final class AlignmentLineKt {
ctor public AlignmentLineKt();
method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-layout/api/public_plus_experimental_0.1.0-dev03.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/public_plus_experimental_0.1.0-dev03.txt
+++ b/ui/ui-layout/api/public_plus_experimental_0.1.0-dev03.txt
@@ -7,6 +7,40 @@
method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class Aligned {
+ method public androidx.ui.core.LayoutModifier getBottom();
+ method public androidx.ui.core.LayoutModifier getBottomCenter();
+ method public androidx.ui.core.LayoutModifier getBottomLeft();
+ method public androidx.ui.core.LayoutModifier getBottomRight();
+ method public androidx.ui.core.LayoutModifier getCenter();
+ method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+ method public androidx.ui.core.LayoutModifier getCenterLeft();
+ method public androidx.ui.core.LayoutModifier getCenterRight();
+ method public androidx.ui.core.LayoutModifier getCenterVertically();
+ method public androidx.ui.core.LayoutModifier getEnd();
+ method public androidx.ui.core.LayoutModifier getStart();
+ method public androidx.ui.core.LayoutModifier getTop();
+ method public androidx.ui.core.LayoutModifier getTopCenter();
+ method public androidx.ui.core.LayoutModifier getTopLeft();
+ method public androidx.ui.core.LayoutModifier getTopRight();
+ property public final androidx.ui.core.LayoutModifier Bottom;
+ property public final androidx.ui.core.LayoutModifier BottomCenter;
+ property public final androidx.ui.core.LayoutModifier BottomLeft;
+ property public final androidx.ui.core.LayoutModifier BottomRight;
+ property public final androidx.ui.core.LayoutModifier Center;
+ property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+ property public final androidx.ui.core.LayoutModifier CenterLeft;
+ property public final androidx.ui.core.LayoutModifier CenterRight;
+ property public final androidx.ui.core.LayoutModifier CenterVertically;
+ property public final androidx.ui.core.LayoutModifier End;
+ property public final androidx.ui.core.LayoutModifier Start;
+ property public final androidx.ui.core.LayoutModifier Top;
+ property public final androidx.ui.core.LayoutModifier TopCenter;
+ property public final androidx.ui.core.LayoutModifier TopLeft;
+ property public final androidx.ui.core.LayoutModifier TopRight;
+ field public static final androidx.ui.layout.Aligned! INSTANCE;
+ }
+
public final class AlignmentLineKt {
ctor public AlignmentLineKt();
method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/api/public_plus_experimental_current.txt b/ui/ui-layout/api/public_plus_experimental_current.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/public_plus_experimental_current.txt
+++ b/ui/ui-layout/api/public_plus_experimental_current.txt
@@ -7,6 +7,40 @@
method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class Aligned {
+ method public androidx.ui.core.LayoutModifier getBottom();
+ method public androidx.ui.core.LayoutModifier getBottomCenter();
+ method public androidx.ui.core.LayoutModifier getBottomLeft();
+ method public androidx.ui.core.LayoutModifier getBottomRight();
+ method public androidx.ui.core.LayoutModifier getCenter();
+ method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+ method public androidx.ui.core.LayoutModifier getCenterLeft();
+ method public androidx.ui.core.LayoutModifier getCenterRight();
+ method public androidx.ui.core.LayoutModifier getCenterVertically();
+ method public androidx.ui.core.LayoutModifier getEnd();
+ method public androidx.ui.core.LayoutModifier getStart();
+ method public androidx.ui.core.LayoutModifier getTop();
+ method public androidx.ui.core.LayoutModifier getTopCenter();
+ method public androidx.ui.core.LayoutModifier getTopLeft();
+ method public androidx.ui.core.LayoutModifier getTopRight();
+ property public final androidx.ui.core.LayoutModifier Bottom;
+ property public final androidx.ui.core.LayoutModifier BottomCenter;
+ property public final androidx.ui.core.LayoutModifier BottomLeft;
+ property public final androidx.ui.core.LayoutModifier BottomRight;
+ property public final androidx.ui.core.LayoutModifier Center;
+ property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+ property public final androidx.ui.core.LayoutModifier CenterLeft;
+ property public final androidx.ui.core.LayoutModifier CenterRight;
+ property public final androidx.ui.core.LayoutModifier CenterVertically;
+ property public final androidx.ui.core.LayoutModifier End;
+ property public final androidx.ui.core.LayoutModifier Start;
+ property public final androidx.ui.core.LayoutModifier Top;
+ property public final androidx.ui.core.LayoutModifier TopCenter;
+ property public final androidx.ui.core.LayoutModifier TopLeft;
+ property public final androidx.ui.core.LayoutModifier TopRight;
+ field public static final androidx.ui.layout.Aligned! INSTANCE;
+ }
+
public final class AlignmentLineKt {
ctor public AlignmentLineKt();
method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/api/restricted_0.1.0-dev03.txt b/ui/ui-layout/api/restricted_0.1.0-dev03.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/restricted_0.1.0-dev03.txt
+++ b/ui/ui-layout/api/restricted_0.1.0-dev03.txt
@@ -7,6 +7,40 @@
method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class Aligned {
+ method public androidx.ui.core.LayoutModifier getBottom();
+ method public androidx.ui.core.LayoutModifier getBottomCenter();
+ method public androidx.ui.core.LayoutModifier getBottomLeft();
+ method public androidx.ui.core.LayoutModifier getBottomRight();
+ method public androidx.ui.core.LayoutModifier getCenter();
+ method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+ method public androidx.ui.core.LayoutModifier getCenterLeft();
+ method public androidx.ui.core.LayoutModifier getCenterRight();
+ method public androidx.ui.core.LayoutModifier getCenterVertically();
+ method public androidx.ui.core.LayoutModifier getEnd();
+ method public androidx.ui.core.LayoutModifier getStart();
+ method public androidx.ui.core.LayoutModifier getTop();
+ method public androidx.ui.core.LayoutModifier getTopCenter();
+ method public androidx.ui.core.LayoutModifier getTopLeft();
+ method public androidx.ui.core.LayoutModifier getTopRight();
+ property public final androidx.ui.core.LayoutModifier Bottom;
+ property public final androidx.ui.core.LayoutModifier BottomCenter;
+ property public final androidx.ui.core.LayoutModifier BottomLeft;
+ property public final androidx.ui.core.LayoutModifier BottomRight;
+ property public final androidx.ui.core.LayoutModifier Center;
+ property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+ property public final androidx.ui.core.LayoutModifier CenterLeft;
+ property public final androidx.ui.core.LayoutModifier CenterRight;
+ property public final androidx.ui.core.LayoutModifier CenterVertically;
+ property public final androidx.ui.core.LayoutModifier End;
+ property public final androidx.ui.core.LayoutModifier Start;
+ property public final androidx.ui.core.LayoutModifier Top;
+ property public final androidx.ui.core.LayoutModifier TopCenter;
+ property public final androidx.ui.core.LayoutModifier TopLeft;
+ property public final androidx.ui.core.LayoutModifier TopRight;
+ field public static final androidx.ui.layout.Aligned! INSTANCE;
+ }
+
public final class AlignmentLineKt {
ctor public AlignmentLineKt();
method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/api/restricted_current.txt b/ui/ui-layout/api/restricted_current.txt
index 09d1e0f..a9abbf5 100644
--- a/ui/ui-layout/api/restricted_current.txt
+++ b/ui/ui-layout/api/restricted_current.txt
@@ -7,6 +7,40 @@
method public static void Center(kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
+ public final class Aligned {
+ method public androidx.ui.core.LayoutModifier getBottom();
+ method public androidx.ui.core.LayoutModifier getBottomCenter();
+ method public androidx.ui.core.LayoutModifier getBottomLeft();
+ method public androidx.ui.core.LayoutModifier getBottomRight();
+ method public androidx.ui.core.LayoutModifier getCenter();
+ method public androidx.ui.core.LayoutModifier getCenterHorizontally();
+ method public androidx.ui.core.LayoutModifier getCenterLeft();
+ method public androidx.ui.core.LayoutModifier getCenterRight();
+ method public androidx.ui.core.LayoutModifier getCenterVertically();
+ method public androidx.ui.core.LayoutModifier getEnd();
+ method public androidx.ui.core.LayoutModifier getStart();
+ method public androidx.ui.core.LayoutModifier getTop();
+ method public androidx.ui.core.LayoutModifier getTopCenter();
+ method public androidx.ui.core.LayoutModifier getTopLeft();
+ method public androidx.ui.core.LayoutModifier getTopRight();
+ property public final androidx.ui.core.LayoutModifier Bottom;
+ property public final androidx.ui.core.LayoutModifier BottomCenter;
+ property public final androidx.ui.core.LayoutModifier BottomLeft;
+ property public final androidx.ui.core.LayoutModifier BottomRight;
+ property public final androidx.ui.core.LayoutModifier Center;
+ property public final androidx.ui.core.LayoutModifier CenterHorizontally;
+ property public final androidx.ui.core.LayoutModifier CenterLeft;
+ property public final androidx.ui.core.LayoutModifier CenterRight;
+ property public final androidx.ui.core.LayoutModifier CenterVertically;
+ property public final androidx.ui.core.LayoutModifier End;
+ property public final androidx.ui.core.LayoutModifier Start;
+ property public final androidx.ui.core.LayoutModifier Top;
+ property public final androidx.ui.core.LayoutModifier TopCenter;
+ property public final androidx.ui.core.LayoutModifier TopLeft;
+ property public final androidx.ui.core.LayoutModifier TopRight;
+ field public static final androidx.ui.layout.Aligned! INSTANCE;
+ }
+
public final class AlignmentLineKt {
ctor public AlignmentLineKt();
method public static void AlignmentLineOffset(androidx.ui.core.AlignmentLine alignmentLine, androidx.ui.core.Dp before = 0.dp, androidx.ui.core.Dp after = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
diff --git a/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/AlignSample.kt b/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/AlignSample.kt
index 0e94d80..69af541 100644
--- a/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/AlignSample.kt
+++ b/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/AlignSample.kt
@@ -22,6 +22,7 @@
import androidx.ui.core.dp
import androidx.ui.graphics.Color
import androidx.ui.layout.Align
+import androidx.ui.layout.Aligned
import androidx.ui.layout.Center
import androidx.ui.layout.Column
import androidx.ui.layout.ExpandedHeight
@@ -47,6 +48,18 @@
@Sampled
@Composable
+fun SimpleAlignedModifier() {
+ SizedRectangle(modifier = Aligned.TopCenter, color = Color.Blue, width = 20.dp, height = 20.dp)
+}
+
+@Sampled
+@Composable
+fun SimpleVerticallyAlignedModifier() {
+ SizedRectangle(modifier = Aligned.CenterVertically, color = Color.Blue, height = 50.dp)
+}
+
+@Sampled
+@Composable
fun SimpleGravityInRow() {
Row(ExpandedHeight) {
// The child with no gravity modifier is positioned by default so that its top edge is
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
index 1442e18..2de62fa 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
@@ -32,8 +32,12 @@
import androidx.ui.core.px
import androidx.ui.core.withDensity
import androidx.ui.layout.Align
+import androidx.ui.layout.Aligned
import androidx.ui.layout.AspectRatio
import androidx.ui.layout.Container
+import androidx.ui.layout.ExpandedHeight
+import androidx.ui.layout.Size
+import androidx.ui.layout.Width
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -85,6 +89,83 @@
}
@Test
+ fun test2DAlignedModifier() = withDensity(density) {
+ val sizeDp = 50.dp
+ val size = sizeDp.toIntPx()
+
+ val positionedLatch = CountDownLatch(2)
+ val alignSize = Ref<PxSize>()
+ val alignPosition = Ref<PxPosition>()
+ val childSize = Ref<PxSize>()
+ val childPosition = Ref<PxPosition>()
+ show {
+ Container {
+ SaveLayoutInfo(
+ size = alignSize,
+ position = alignPosition,
+ positionedLatch = positionedLatch
+ )
+ Container(modifier = Aligned.BottomRight wraps Size(sizeDp, sizeDp)) {
+ SaveLayoutInfo(
+ size = childSize,
+ position = childPosition,
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ }
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ val root = findAndroidComposeView()
+ waitForDraw(root)
+
+ assertEquals(PxSize(root.width.px, root.height.px), alignSize.value)
+ assertEquals(PxPosition(0.px, 0.px), alignPosition.value)
+ assertEquals(PxSize(size, size), childSize.value)
+ assertEquals(
+ PxPosition(root.width.px - size, root.height.px - size),
+ childPosition.value
+ )
+ }
+
+ @Test
+ fun test1DAlignedModifier() = withDensity(density) {
+ val sizeDp = 50.dp
+ val size = sizeDp.toIntPx()
+
+ val positionedLatch = CountDownLatch(2)
+ val alignSize = Ref<PxSize>()
+ val alignPosition = Ref<PxPosition>()
+ val childSize = Ref<PxSize>()
+ val childPosition = Ref<PxPosition>()
+ show {
+ Container {
+ SaveLayoutInfo(
+ size = alignSize,
+ position = alignPosition,
+ positionedLatch = positionedLatch
+ )
+ Container(modifier = Aligned.End wraps ExpandedHeight wraps Width(sizeDp)) {
+ SaveLayoutInfo(
+ size = childSize,
+ position = childPosition,
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ }
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ val root = findAndroidComposeView()
+ waitForDraw(root)
+
+ assertEquals(PxSize(root.width.px, root.height.px), alignSize.value)
+ assertEquals(PxPosition(0.px, 0.px), alignPosition.value)
+ assertEquals(PxSize(size, root.height.ipx), childSize.value)
+ assertEquals(PxPosition(root.width.px - size, 0.px), childPosition.value)
+ }
+
+ @Test
fun testAlign_wrapsContent_whenMeasuredWithInfiniteConstraints() = withDensity(density) {
val sizeDp = 50.dp
val size = sizeDp.toIntPx()
@@ -131,6 +212,55 @@
assertEquals(PxPosition(0.px, 0.px), childPosition.value)
}
+ @Test
+ fun testAlignedModifier_wrapsContent_whenMeasuredWithInfiniteConstraints() = withDensity(
+ density
+ ) {
+ val sizeDp = 50.dp
+ val size = sizeDp.toIntPx()
+
+ val positionedLatch = CountDownLatch(2)
+ val alignSize = Ref<PxSize>()
+ val alignPosition = Ref<PxPosition>()
+ val childSize = Ref<PxSize>()
+ val childPosition = Ref<PxPosition>()
+ show {
+ Layout(
+ children = {
+ Container {
+ SaveLayoutInfo(
+ size = alignSize,
+ position = alignPosition,
+ positionedLatch = positionedLatch
+ )
+ Container(modifier = Aligned.BottomRight wraps Size(sizeDp, sizeDp)) {
+ SaveLayoutInfo(
+ size = childSize,
+ position = childPosition,
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ },
+ measureBlock = { measurables, constraints ->
+ val placeable = measurables.first().measure(Constraints())
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ placeable.place(0.ipx, 0.ipx)
+ }
+ }
+ )
+ }
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ val root = findAndroidComposeView()
+ waitForDraw(root)
+
+ assertEquals(PxSize(size, size), alignSize.value)
+ assertEquals(PxPosition(0.px, 0.px), alignPosition.value)
+ assertEquals(PxSize(size, size), childSize.value)
+ assertEquals(PxPosition(0.px, 0.px), childPosition.value)
+ }
+
// TODO(popam): this should be unit test instead
@Test
fun testAlignmentCoordinates_evenSize() {
@@ -184,6 +314,61 @@
}
@Test
+ fun test2DAlignedModifier_hasCorrectIntrinsicMeasurements() = withDensity(density) {
+ testIntrinsics(@Composable {
+ Container(Aligned.TopLeft wraps AspectRatio(2f)) { }
+ }) { minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, maxIntrinsicHeight ->
+ // Min width.
+ assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+ assertEquals(25.dp.toIntPx() * 2, minIntrinsicWidth(25.dp.toIntPx()))
+ assertEquals(0.dp.toIntPx(), minIntrinsicWidth(IntPx.Infinity))
+
+ // Min height.
+ assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+ assertEquals(50.dp.toIntPx() / 2, minIntrinsicHeight(50.dp.toIntPx()))
+ assertEquals(0.dp.toIntPx(), minIntrinsicHeight(IntPx.Infinity))
+
+ // Max width.
+ assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+ assertEquals(25.dp.toIntPx() * 2, maxIntrinsicWidth(25.dp.toIntPx()))
+ assertEquals(0.dp.toIntPx(), maxIntrinsicWidth(IntPx.Infinity))
+
+ // Max height.
+ assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+ assertEquals(50.dp.toIntPx() / 2, maxIntrinsicHeight(50.dp.toIntPx()))
+ assertEquals(0.dp.toIntPx(), maxIntrinsicHeight(IntPx.Infinity))
+ }
+ }
+
+ @Test
+ fun test1DAlignedModifier_hasCorrectIntrinsicMeasurements() = withDensity(density) {
+ testIntrinsics(@Composable {
+ Container(Aligned.CenterVertically wraps AspectRatio(2f)) { }
+ }) { minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, maxIntrinsicHeight ->
+
+ // Min width.
+ assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+ assertEquals(25.dp.toIntPx() * 2, minIntrinsicWidth(25.dp.toIntPx()))
+ assertEquals(0.dp.toIntPx(), minIntrinsicWidth(IntPx.Infinity))
+
+ // Min height.
+ assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+ assertEquals(50.dp.toIntPx() / 2, minIntrinsicHeight(50.dp.toIntPx()))
+ assertEquals(0.dp.toIntPx(), minIntrinsicHeight(IntPx.Infinity))
+
+ // Max width.
+ assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+ assertEquals(25.dp.toIntPx() * 2, maxIntrinsicWidth(25.dp.toIntPx()))
+ assertEquals(0.dp.toIntPx(), maxIntrinsicWidth(IntPx.Infinity))
+
+ // Max height.
+ assertEquals(0.ipx, minIntrinsicWidth(0.ipx))
+ assertEquals(50.dp.toIntPx() / 2, maxIntrinsicHeight(50.dp.toIntPx()))
+ assertEquals(0.dp.toIntPx(), maxIntrinsicHeight(IntPx.Infinity))
+ }
+ }
+
+ @Test
fun testAlign_hasCorrectIntrinsicMeasurements_whenNoChildren() = withDensity(density) {
testIntrinsics(@Composable {
Align(alignment = Alignment.TopLeft) { }
@@ -257,4 +442,61 @@
childPosition.value
)
}
+
+ @Test
+ fun testAlignedModifier_alignsCorrectly_whenOddDimensions_endAligned() = withDensity(density) {
+ // Given a 100 x 100 pixel container, we want to make sure that when aligning a 1 x 1 pixel
+ // child to both ends (bottom, and right) we correctly position children at the last
+ // possible pixel, and avoid rounding issues. Previously we first centered the coordinates,
+ // and then aligned after, so the maths would actually be (99 / 2) * 2, which incorrectly
+ // ends up at 100 (IntPx rounds up) - so the last pixels in both directions just wouldn't
+ // be visible.
+ val parentSize = 100.ipx.toDp()
+ val childSizeDp = 1.ipx.toDp()
+ val childSizeIpx = childSizeDp.toIntPx()
+
+ val positionedLatch = CountDownLatch(2)
+ val alignSize = Ref<PxSize>()
+ val alignPosition = Ref<PxPosition>()
+ val childSize = Ref<PxSize>()
+ val childPosition = Ref<PxPosition>()
+ show {
+ Layout(
+ children = {
+ Container(Size(parentSize, parentSize)) {
+ SaveLayoutInfo(
+ size = alignSize,
+ position = alignPosition,
+ positionedLatch = positionedLatch
+ )
+ Container(Aligned.BottomRight wraps Size(childSizeDp, childSizeDp)) {
+ SaveLayoutInfo(
+ size = childSize,
+ position = childPosition,
+ positionedLatch = positionedLatch
+ )
+ }
+ }
+ }, measureBlock = { measurables, constraints ->
+ val placeable = measurables.first().measure(Constraints())
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ placeable.place(0.ipx, 0.ipx)
+ }
+ }
+ )
+ }
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ val root = findAndroidComposeView()
+ waitForDraw(root)
+
+ assertEquals(PxSize(childSizeIpx, childSizeIpx), childSize.value)
+ assertEquals(
+ PxPosition(
+ alignSize.value!!.width - childSizeIpx,
+ alignSize.value!!.height - childSizeIpx
+ ),
+ childPosition.value
+ )
+ }
}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
index d272906..fd7b567 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
@@ -22,6 +22,11 @@
import androidx.ui.core.looseMin
import androidx.compose.Composable
import androidx.ui.core.Alignment
+import androidx.ui.core.Constraints
+import androidx.ui.core.DensityScope
+import androidx.ui.core.IntPxPosition
+import androidx.ui.core.LayoutModifier
+import androidx.ui.core.ipx
/**
* A layout that takes a child and aligns it within itself, according to the alignment parameter.
@@ -90,7 +95,7 @@
* by the parent layout rather than the child itself. Different layout models allow different
* [Gravity] options. For example, [Row] provides Top and Bottom, while [Column] provides
* Start and End.
- * Unlike [Align], layout children with [Gravity] are aligned only after the size
+ * Unlike [Aligned], layout children with [Gravity] are aligned only after the size
* of the parent is known, therefore not affecting the size of the parent in order to achieve
* their own alignment.
*
@@ -101,3 +106,161 @@
* @sample androidx.ui.layout.samples.SimpleGravityInColumn
*/
object Gravity
+
+/**
+ * Provides alignment options for a target layout where the alignment is handled by the modifier
+ * itself (rather than by the layout's parent). To achieve this, the modifier tries to fill the
+ * available space and align the target layout within itself. If the incoming constraints are
+ * infinite, the modifier will wrap the child instead and the alignment will not be achieved.
+ *
+ * Example usage:
+ *
+ * @sample androidx.ui.layout.samples.SimpleAlignedModifier
+ * @sample androidx.ui.layout.samples.SimpleVerticallyAlignedModifier
+ */
+object Aligned {
+ /**
+ * A layout modifier that positions the target component inside its parent to the top in
+ * vertical direction and wraps the component in horizontal direction.
+ */
+ val Top: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.TopLeft, direction = Direction.Vertical)
+
+ /**
+ * A layout modifier that positions the target component in the center of the parent in
+ * vertical direction and wraps the component in horizontal direction.
+ */
+ val CenterVertically: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.CenterLeft, direction = Direction.Vertical)
+
+ /**
+ * A layout modifier that positions the target component inside its parent to the bottom in
+ * vertical direction and wraps the component in horizontal direction.
+ */
+ val Bottom: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.BottomLeft, direction = Direction.Vertical)
+
+ /**
+ * A layout modifier that positions the target component inside its parent to the start edge
+ * in horizontal direction and wraps the component in vertical direction.
+ */
+ val Start: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.TopLeft, direction = Direction.Horizontal)
+
+ /**
+ * A layout modifier that positions the target component in the center of the parent in
+ * horizontal direction and wraps the component in vertical direction.
+ */
+ val CenterHorizontally: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.TopCenter, direction = Direction.Horizontal)
+
+ /**
+ * A layout modifier that positions the target component inside its parent to the end edge
+ * in horizontal direction and wraps the component in vertical direction.
+ */
+ val End: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.TopRight, direction = Direction.Horizontal)
+
+ /**
+ * A layout modifier that positions the target component top-left inside its parent.
+ */
+ val TopLeft: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.TopLeft, direction = Direction.Both)
+
+ /**
+ * A layout modifier that positions the target component top-center inside its parent.
+ */
+ val TopCenter: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.TopCenter, direction = Direction.Both)
+
+ /**
+ * A layout modifier that positions the target component top-right inside its parent.
+ */
+ val TopRight: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.TopRight, direction = Direction.Both)
+
+ /**
+ * A layout modifier that positions the target component center-left inside its parent.
+ */
+ val CenterLeft: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.CenterLeft, direction = Direction.Both)
+
+ /**
+ * A layout modifier that positions the target component in the center of its parent.
+ */
+ val Center: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.Center, direction = Direction.Both)
+
+ /**
+ * A layout modifier that positions the target component center-right inside its parent.
+ */
+ val CenterRight: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.CenterRight, direction = Direction.Both)
+
+ /**
+ * A layout modifier that positions the target component bottom-left inside its parent.
+ */
+ val BottomLeft: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.BottomLeft, direction = Direction.Both)
+
+ /**
+ * A layout modifier that positions the target component bottom-center inside its parent.
+ */
+ val BottomCenter: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.BottomCenter, direction = Direction.Both)
+
+ /**
+ * A layout modifier that positions the target component bottom-right inside its parent.
+ */
+ val BottomRight: LayoutModifier =
+ AlignmentModifier(alignment = Alignment.BottomRight, direction = Direction.Both)
+}
+
+private enum class Direction {
+ Vertical, Horizontal, Both
+}
+
+private data class AlignmentModifier(
+ private val alignment: Alignment,
+ private val direction: Direction
+) : LayoutModifier {
+ override fun DensityScope.modifyConstraints(constraints: Constraints) = when (direction) {
+ Direction.Both -> constraints.looseMin()
+ Direction.Horizontal -> constraints.copy(minWidth = 0.ipx)
+ Direction.Vertical -> constraints.copy(minHeight = 0.ipx)
+ }
+
+ override fun DensityScope.modifySize(
+ constraints: Constraints,
+ childSize: IntPxSize
+ ): IntPxSize {
+ val width = if (
+ direction != Direction.Vertical && constraints.maxWidth.isFinite()
+ ) {
+ constraints.maxWidth
+ } else {
+ childSize.width
+ }
+ val height = if (
+ direction != Direction.Horizontal && constraints.maxHeight.isFinite()
+ ) {
+ constraints.maxHeight
+ } else {
+ childSize.height
+ }
+ return IntPxSize(width, height)
+ }
+
+ override fun DensityScope.modifyPosition(
+ childPosition: IntPxPosition,
+ childSize: IntPxSize,
+ containerSize: IntPxSize
+ ): IntPxPosition {
+ return alignment.align(
+ IntPxSize(
+ containerSize.width - childSize.width,
+ containerSize.height - childSize.height
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-material/api/0.1.0-dev03.txt b/ui/ui-material/api/0.1.0-dev03.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/0.1.0-dev03.txt
+++ b/ui/ui-material/api/0.1.0-dev03.txt
@@ -70,7 +70,8 @@
public final class ColorKt {
ctor public ColorKt();
- method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+ method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+ method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
}
public interface ColorPalette {
@@ -86,8 +87,10 @@
method public androidx.ui.graphics.Color getSecondary();
method public androidx.ui.graphics.Color getSecondaryVariant();
method public androidx.ui.graphics.Color getSurface();
+ method public boolean isLight();
property public abstract androidx.ui.graphics.Color background;
property public abstract androidx.ui.graphics.Color error;
+ property public abstract boolean isLight;
property public abstract androidx.ui.graphics.Color onBackground;
property public abstract androidx.ui.graphics.Color onError;
property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
public final class MaterialThemeKt {
ctor public MaterialThemeKt();
- method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -70,7 +70,8 @@
public final class ColorKt {
ctor public ColorKt();
- method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+ method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+ method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
}
public interface ColorPalette {
@@ -86,8 +87,10 @@
method public androidx.ui.graphics.Color getSecondary();
method public androidx.ui.graphics.Color getSecondaryVariant();
method public androidx.ui.graphics.Color getSurface();
+ method public boolean isLight();
property public abstract androidx.ui.graphics.Color background;
property public abstract androidx.ui.graphics.Color error;
+ property public abstract boolean isLight;
property public abstract androidx.ui.graphics.Color onBackground;
property public abstract androidx.ui.graphics.Color onError;
property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
public final class MaterialThemeKt {
ctor public MaterialThemeKt();
- method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-material/api/public_plus_experimental_0.1.0-dev03.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/public_plus_experimental_0.1.0-dev03.txt
+++ b/ui/ui-material/api/public_plus_experimental_0.1.0-dev03.txt
@@ -70,7 +70,8 @@
public final class ColorKt {
ctor public ColorKt();
- method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+ method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+ method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
}
public interface ColorPalette {
@@ -86,8 +87,10 @@
method public androidx.ui.graphics.Color getSecondary();
method public androidx.ui.graphics.Color getSecondaryVariant();
method public androidx.ui.graphics.Color getSurface();
+ method public boolean isLight();
property public abstract androidx.ui.graphics.Color background;
property public abstract androidx.ui.graphics.Color error;
+ property public abstract boolean isLight;
property public abstract androidx.ui.graphics.Color onBackground;
property public abstract androidx.ui.graphics.Color onError;
property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
public final class MaterialThemeKt {
ctor public MaterialThemeKt();
- method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/api/public_plus_experimental_current.txt b/ui/ui-material/api/public_plus_experimental_current.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/public_plus_experimental_current.txt
+++ b/ui/ui-material/api/public_plus_experimental_current.txt
@@ -70,7 +70,8 @@
public final class ColorKt {
ctor public ColorKt();
- method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+ method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+ method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
}
public interface ColorPalette {
@@ -86,8 +87,10 @@
method public androidx.ui.graphics.Color getSecondary();
method public androidx.ui.graphics.Color getSecondaryVariant();
method public androidx.ui.graphics.Color getSurface();
+ method public boolean isLight();
property public abstract androidx.ui.graphics.Color background;
property public abstract androidx.ui.graphics.Color error;
+ property public abstract boolean isLight;
property public abstract androidx.ui.graphics.Color onBackground;
property public abstract androidx.ui.graphics.Color onError;
property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
public final class MaterialThemeKt {
ctor public MaterialThemeKt();
- method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/api/restricted_0.1.0-dev03.txt b/ui/ui-material/api/restricted_0.1.0-dev03.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/restricted_0.1.0-dev03.txt
+++ b/ui/ui-material/api/restricted_0.1.0-dev03.txt
@@ -70,7 +70,8 @@
public final class ColorKt {
ctor public ColorKt();
- method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+ method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+ method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
}
public interface ColorPalette {
@@ -86,8 +87,10 @@
method public androidx.ui.graphics.Color getSecondary();
method public androidx.ui.graphics.Color getSecondaryVariant();
method public androidx.ui.graphics.Color getSurface();
+ method public boolean isLight();
property public abstract androidx.ui.graphics.Color background;
property public abstract androidx.ui.graphics.Color error;
+ property public abstract boolean isLight;
property public abstract androidx.ui.graphics.Color onBackground;
property public abstract androidx.ui.graphics.Color onError;
property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
public final class MaterialThemeKt {
ctor public MaterialThemeKt();
- method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index 0733899..61f8238 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -70,7 +70,8 @@
public final class ColorKt {
ctor public ColorKt();
- method public static androidx.ui.material.ColorPalette ColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
+ method public static androidx.ui.material.ColorPalette darkColorPalette(androidx.ui.graphics.Color primary = Color(4290479868), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color background = Color(4279374354), androidx.ui.graphics.Color surface = Color(4279374354), androidx.ui.graphics.Color error = Color(4291782265), androidx.ui.graphics.Color onPrimary = Color.Black, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.White, androidx.ui.graphics.Color onSurface = Color.White, androidx.ui.graphics.Color onError = Color.Black);
+ method public static androidx.ui.material.ColorPalette lightColorPalette(androidx.ui.graphics.Color primary = Color(4284612846), androidx.ui.graphics.Color primaryVariant = Color(4281794739), androidx.ui.graphics.Color secondary = Color(4278442694), androidx.ui.graphics.Color secondaryVariant = Color(4278290310), androidx.ui.graphics.Color background = Color.White, androidx.ui.graphics.Color surface = Color.White, androidx.ui.graphics.Color error = Color(4289724448), androidx.ui.graphics.Color onPrimary = Color.White, androidx.ui.graphics.Color onSecondary = Color.Black, androidx.ui.graphics.Color onBackground = Color.Black, androidx.ui.graphics.Color onSurface = Color.Black, androidx.ui.graphics.Color onError = Color.White);
}
public interface ColorPalette {
@@ -86,8 +87,10 @@
method public androidx.ui.graphics.Color getSecondary();
method public androidx.ui.graphics.Color getSecondaryVariant();
method public androidx.ui.graphics.Color getSurface();
+ method public boolean isLight();
property public abstract androidx.ui.graphics.Color background;
property public abstract androidx.ui.graphics.Color error;
+ property public abstract boolean isLight;
property public abstract androidx.ui.graphics.Color onBackground;
property public abstract androidx.ui.graphics.Color onError;
property public abstract androidx.ui.graphics.Color onPrimary;
@@ -185,7 +188,7 @@
public final class MaterialThemeKt {
ctor public MaterialThemeKt();
- method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = ColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
+ method public static void MaterialTheme(androidx.ui.material.ColorPalette colors = lightColorPalette(), androidx.ui.material.Typography typography = androidx.ui.material.Typography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
public final class ProgressIndicatorKt {
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
index cbc2338..69edd80 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/AppBarActivity.kt
@@ -22,9 +22,13 @@
import androidx.ui.core.Alignment
import androidx.ui.core.Text
import androidx.ui.core.dp
+import androidx.ui.graphics.imageFromResource
+import androidx.ui.layout.Arrangement
import androidx.ui.layout.Column
import androidx.ui.layout.Container
+import androidx.ui.layout.ExpandedHeight
import androidx.ui.layout.FlexColumn
+import androidx.ui.material.MaterialTheme
import androidx.ui.material.RadioGroup
import androidx.ui.material.demos.AppBarActivity.BottomAppBarOption.CenterFab
import androidx.ui.material.demos.AppBarActivity.BottomAppBarOption.CutoutFab
@@ -34,19 +38,14 @@
import androidx.ui.material.demos.AppBarActivity.BottomAppBarOption.NoFab
import androidx.ui.material.demos.AppBarActivity.TopAppBarOption.Actions
import androidx.ui.material.demos.AppBarActivity.TopAppBarOption.Simple
-import androidx.ui.material.samples.SimpleBottomAppBarCutoutFab
import androidx.ui.material.samples.SimpleBottomAppBarCenterFab
+import androidx.ui.material.samples.SimpleBottomAppBarCutoutFab
import androidx.ui.material.samples.SimpleBottomAppBarEndFab
import androidx.ui.material.samples.SimpleBottomAppBarExtendedCutoutFab
import androidx.ui.material.samples.SimpleBottomAppBarFancyAnimatingCutoutFab
import androidx.ui.material.samples.SimpleBottomAppBarNoFab
import androidx.ui.material.samples.SimpleTopAppBarNavIcon
import androidx.ui.material.samples.SimpleTopAppBarNavIconWithActions
-import androidx.ui.material.surface.Surface
-import androidx.ui.graphics.imageFromResource
-import androidx.ui.layout.Arrangement
-import androidx.ui.layout.ExpandedHeight
-import androidx.ui.material.MaterialTheme
class AppBarActivity : MaterialDemoActivity() {
@@ -77,65 +76,63 @@
var selectedTopAppBar by +state { Simple }
var selectedBottomAppBar by +state { NoFab }
- Surface {
- FlexColumn {
- inflexible {
- Container(height = 120.dp, alignment = Alignment.TopCenter) {
- when (selectedTopAppBar) {
- Simple -> SimpleTopAppBarNavIcon(navigationImage)
- Actions -> SimpleTopAppBarNavIconWithActions(
- favouriteImage,
- navigationImage
+ FlexColumn {
+ inflexible {
+ Container(height = 120.dp, alignment = Alignment.TopCenter) {
+ when (selectedTopAppBar) {
+ Simple -> SimpleTopAppBarNavIcon(navigationImage)
+ Actions -> SimpleTopAppBarNavIconWithActions(
+ favouriteImage,
+ navigationImage
+ )
+ }
+ }
+ }
+ flexible(1f) {
+ Column(ExpandedHeight, arrangement = Arrangement.SpaceBetween) {
+ DemoText("TopAppBar options")
+ RadioGroup {
+ topAppBarOptions.forEach { topAppBar ->
+ RadioGroupTextItem(
+ selected = (topAppBar == selectedTopAppBar),
+ onSelect = { selectedTopAppBar = topAppBar },
+ text = topAppBar.description
+ )
+ }
+ }
+ DemoText("BottomAppBar options")
+ RadioGroup {
+ bottomAppBarOptions.forEach { bottomAppBar ->
+ RadioGroupTextItem(
+ selected = (bottomAppBar == selectedBottomAppBar),
+ onSelect = { selectedBottomAppBar = bottomAppBar },
+ text = bottomAppBar.description
)
}
}
}
- flexible(1f) {
- Column(ExpandedHeight, arrangement = Arrangement.SpaceBetween) {
- DemoText("TopAppBar options")
- RadioGroup {
- topAppBarOptions.forEach { topAppBar ->
- RadioGroupTextItem(
- selected = (topAppBar == selectedTopAppBar),
- onSelect = { selectedTopAppBar = topAppBar },
- text = topAppBar.description
- )
- }
- }
- DemoText("BottomAppBar options")
- RadioGroup {
- bottomAppBarOptions.forEach { bottomAppBar ->
- RadioGroupTextItem(
- selected = (bottomAppBar == selectedBottomAppBar),
- onSelect = { selectedBottomAppBar = bottomAppBar },
- text = bottomAppBar.description
- )
- }
- }
- }
- }
- inflexible {
- Container(height = 120.dp, alignment = Alignment.BottomCenter) {
- when (selectedBottomAppBar) {
- NoFab -> SimpleBottomAppBarNoFab(favouriteImage, navigationImage)
- CenterFab -> SimpleBottomAppBarCenterFab(
- favouriteImage,
- navigationImage
- )
- EndFab -> SimpleBottomAppBarEndFab(favouriteImage)
- CutoutFab -> SimpleBottomAppBarCutoutFab(
- favouriteImage,
- navigationImage
- )
- ExtendedCutoutFab -> SimpleBottomAppBarExtendedCutoutFab(
- favouriteImage,
- navigationImage
- )
- FancyAnimatingCutoutFab -> SimpleBottomAppBarFancyAnimatingCutoutFab(
- favouriteImage,
- navigationImage
- )
- }
+ }
+ inflexible {
+ Container(height = 120.dp, alignment = Alignment.BottomCenter) {
+ when (selectedBottomAppBar) {
+ NoFab -> SimpleBottomAppBarNoFab(favouriteImage, navigationImage)
+ CenterFab -> SimpleBottomAppBarCenterFab(
+ favouriteImage,
+ navigationImage
+ )
+ EndFab -> SimpleBottomAppBarEndFab(favouriteImage)
+ CutoutFab -> SimpleBottomAppBarCutoutFab(
+ favouriteImage,
+ navigationImage
+ )
+ ExtendedCutoutFab -> SimpleBottomAppBarExtendedCutoutFab(
+ favouriteImage,
+ navigationImage
+ )
+ FancyAnimatingCutoutFab -> SimpleBottomAppBarFancyAnimatingCutoutFab(
+ favouriteImage,
+ navigationImage
+ )
}
}
}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DataTableActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DataTableActivity.kt
index 8d92f88..0c04a5a 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DataTableActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DataTableActivity.kt
@@ -18,14 +18,11 @@
import androidx.compose.Composable
import androidx.ui.material.samples.SimpleDataTable
-import androidx.ui.material.surface.Surface
class DataTableActivity : MaterialDemoActivity() {
@Composable
override fun materialContent() {
- Surface {
- SimpleDataTable()
- }
+ SimpleDataTable()
}
}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
index 484f12f..ec30f46 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/DynamicThemeActivity.kt
@@ -50,6 +50,7 @@
import androidx.ui.material.FloatingActionButton
import androidx.ui.material.MaterialTheme
import androidx.ui.material.TopAppBar
+import androidx.ui.material.lightColorPalette
import androidx.ui.material.surface.Surface
import androidx.ui.text.TextStyle
import kotlin.math.round
@@ -166,7 +167,7 @@
val secondary = lerp(Color(0xFF03DAC6), Color(0xFFBB86FC), interpolatedFraction)
val background = lerp(Color.White, Color(0xFF121212), interpolatedFraction)
- return ColorPalette(
+ return lightColorPalette(
primary = primary,
secondary = secondary,
background = background
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
index 98ccd23..4a91328a 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/MaterialDemoActivity.kt
@@ -26,21 +26,29 @@
import androidx.compose.Composable
import androidx.compose.FrameManager
import androidx.compose.Model
+import androidx.compose.unaryPlus
import androidx.preference.EditTextPreference
+import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import androidx.ui.core.setContent
+import androidx.ui.foundation.isSystemInDarkTheme
import androidx.ui.graphics.Color
import androidx.ui.graphics.toArgb
import androidx.ui.material.ColorPalette
import androidx.ui.material.MaterialTheme
+import androidx.ui.material.darkColorPalette
import androidx.ui.material.demos.MaterialSettingsActivity.SettingsFragment
-import kotlin.random.Random
+import androidx.ui.material.lightColorPalette
+import androidx.ui.material.surface.Surface
import kotlin.reflect.full.memberProperties
@Model
class CurrentColorPalette {
- var colors: ColorPalette = ColorPalette()
+ var lightColors: ColorPalette = lightColorPalette()
+ var darkColors: ColorPalette = darkColorPalette()
+
+ val colors get() = if (+isSystemInDarkTheme()) darkColors else lightColors
}
/**
@@ -49,16 +57,18 @@
*/
abstract class MaterialDemoActivity : Activity() {
- private val currentColors = CurrentColorPalette()
+ private var currentColors = CurrentColorPalette()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Ensure we are in a frame, as this is only normally initialized after the setContent call
FrameManager.ensureStarted()
- currentColors.colors = getColorsFromSharedPreferences()
+ currentColors.getColorsFromSharedPreferences()
setContent {
MaterialTheme(currentColors.colors) {
- materialContent()
+ Surface {
+ materialContent()
+ }
}
}
}
@@ -66,14 +76,15 @@
override fun onResume() {
super.onResume()
// Update colors in case we changed something in settings activity
- currentColors.colors = getColorsFromSharedPreferences()
+ currentColors.getColorsFromSharedPreferences()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- menu?.add(Menu.NONE, SETTINGS, Menu.NONE, "Theme settings")
- menu?.add(Menu.NONE, SHUFFLE, Menu.NONE, "Shuffle colors")
- menu?.add(Menu.NONE, INVERT, Menu.NONE, "Invert color mapping")
- menu?.add(Menu.NONE, RESET, Menu.NONE, "Reset theme to default")
+ menu?.run {
+ add(Menu.NONE, SETTINGS, Menu.NONE, "Theme settings")
+ add(Menu.NONE, SHUFFLE, Menu.NONE, "Shuffle colors")
+ add(Menu.NONE, RESET, Menu.NONE, "Reset theme to default")
+ }
return true
}
@@ -81,37 +92,13 @@
when (item.itemId) {
SETTINGS -> startActivity(Intent(this, MaterialSettingsActivity::class.java))
SHUFFLE -> {
- val colors = generateColorPalette(currentColors.colors)
- colors.saveColors()
- currentColors.colors = colors
- }
- INVERT -> {
- // Flip all colors
- val newPrimary = currentColors.colors.onPrimary
- val newOnPrimary = currentColors.colors.primary
- val newSecondary = currentColors.colors.onSecondary
- val newOnSecondary = currentColors.colors.secondary
- val colors = ColorPalette(
- primary = newPrimary,
- primaryVariant = currentColors.colors.primaryVariant,
- secondary = newSecondary,
- secondaryVariant = currentColors.colors.secondaryVariant,
- background = currentColors.colors.background,
- surface = currentColors.colors.surface,
- error = currentColors.colors.error,
- onPrimary = newOnPrimary,
- onSecondary = newOnSecondary,
- onBackground = currentColors.colors.onBackground,
- onSurface = currentColors.colors.onSurface,
- onError = currentColors.colors.onError
- )
- colors.saveColors()
- currentColors.colors = colors
+ currentColors.shuffleColors()
+ currentColors.saveColors()
}
RESET -> {
val sharedPreferences = getDefaultSharedPreferences(this)
sharedPreferences.edit().clear().apply()
- currentColors.colors = getColorsFromSharedPreferences()
+ currentColors.getColorsFromSharedPreferences()
}
}
return true
@@ -122,30 +109,41 @@
* not present in the [SharedPreferences], its default value as defined in [ColorPalette]
* will be returned.
*/
- private fun getColorsFromSharedPreferences(): ColorPalette {
- val sharedPreferences = getDefaultSharedPreferences(this)
- val function = ::ColorPalette
- val parametersToSet = function.parameters.mapNotNull { parameter ->
- val savedValue = sharedPreferences.getString(parameter.name, "")
- if (savedValue.isNullOrBlank()) {
- null
- } else {
- val parsedColor = Color(java.lang.Long.parseLong(savedValue, 16))
- parameter to parsedColor
- }
- }.toMap()
- if (parametersToSet.isEmpty()) return ColorPalette()
- return ::ColorPalette.callBy(parametersToSet)
+ private fun CurrentColorPalette.getColorsFromSharedPreferences() {
+ val sharedPreferences = getDefaultSharedPreferences(this@MaterialDemoActivity)
+
+ fun getColorsFromSharedPreferences(isLightTheme: Boolean): ColorPalette {
+ val function = if (isLightTheme) ::lightColorPalette else ::darkColorPalette
+ val parametersToSet = function.parameters.mapNotNull { parameter ->
+ val savedValue = sharedPreferences.getString(parameter.name + isLightTheme, "")
+ if (savedValue.isNullOrBlank()) {
+ null
+ } else {
+ val parsedColor = Color(java.lang.Long.parseLong(savedValue, 16))
+ parameter to parsedColor
+ }
+ }.toMap()
+ return function.callBy(parametersToSet)
+ }
+
+ lightColors = getColorsFromSharedPreferences(true)
+ darkColors = getColorsFromSharedPreferences(false)
}
/**
- * Persists the current [ColorPalette] to [SharedPreferences].
+ * Persists the current [CurrentColorPalette] to [SharedPreferences].
*/
- private fun ColorPalette.saveColors() {
- forEachColorProperty { name, color ->
+ private fun CurrentColorPalette.saveColors() {
+ lightColors.forEachColorProperty { name, color ->
getDefaultSharedPreferences(this@MaterialDemoActivity)
.edit()
- .putString(name, Integer.toHexString(color.toArgb()))
+ .putString(name + true, Integer.toHexString(color.toArgb()))
+ .apply()
+ }
+ darkColors.forEachColorProperty { name, color ->
+ getDefaultSharedPreferences(this@MaterialDemoActivity)
+ .edit()
+ .putString(name + false, Integer.toHexString(color.toArgb()))
.apply()
}
}
@@ -155,22 +153,37 @@
* [ColorPalette.secondary] and [ColorPalette.onSecondary] as dark-on-light or light-on-dark
* pairs.
*/
- private fun generateColorPalette(currentColors: ColorPalette): ColorPalette {
- val (primary, onPrimary) = generateColorPair()
- val (secondary, onSecondary) = generateColorPair()
- return ColorPalette(
- primary = primary,
- primaryVariant = currentColors.primaryVariant,
- secondary = secondary,
- secondaryVariant = currentColors.secondaryVariant,
- background = currentColors.background,
- surface = currentColors.surface,
- error = currentColors.error,
- onPrimary = onPrimary,
- onSecondary = onSecondary,
- onBackground = currentColors.onBackground,
- onSurface = currentColors.onSurface,
- onError = currentColors.onError
+ private fun CurrentColorPalette.shuffleColors() {
+ val (lightPrimary, lightOnPrimary) = generateColorPair(true)
+ val (lightSecondary, lightOnSecondary) = generateColorPair(true)
+ lightColors = lightColorPalette(
+ primary = lightPrimary,
+ primaryVariant = lightColors.primaryVariant,
+ secondary = lightSecondary,
+ secondaryVariant = lightColors.secondaryVariant,
+ background = lightColors.background,
+ surface = lightColors.surface,
+ error = lightColors.error,
+ onPrimary = lightOnPrimary,
+ onSecondary = lightOnSecondary,
+ onBackground = lightColors.onBackground,
+ onSurface = lightColors.onSurface,
+ onError = lightColors.onError
+ )
+ val (darkPrimary, darkOnPrimary) = generateColorPair(false)
+ val (darkSecondary, darkOnSecondary) = generateColorPair(false)
+ darkColors = darkColorPalette(
+ primary = darkPrimary,
+ primaryVariant = darkColors.primaryVariant,
+ secondary = darkSecondary,
+ background = darkColors.background,
+ surface = darkColors.surface,
+ error = darkColors.error,
+ onPrimary = darkOnPrimary,
+ onSecondary = darkOnSecondary,
+ onBackground = darkColors.onBackground,
+ onSurface = darkColors.onSurface,
+ onError = darkColors.onError
)
}
@@ -178,14 +191,13 @@
* Generate a random dark and light color from the palette, and returns either a dark-on-light
* or light-on-dark color pair.
*/
- private fun generateColorPair(): Pair<Color, Color> {
+ private fun generateColorPair(isLightTheme: Boolean): Pair<Color, Color> {
val darkColor = Color(DARK_PALETTE_COLORS.random())
val lightColor = Color(LIGHT_PALETTE_COLORS.random())
- val isMainColorLight = Random.nextBoolean()
- return if (isMainColorLight) {
- (lightColor to darkColor)
+ return if (isLightTheme) {
+ darkColor to lightColor
} else {
- (darkColor to lightColor)
+ lightColor to darkColor
}
}
@@ -199,8 +211,7 @@
companion object {
private const val SETTINGS = 1
private const val SHUFFLE = 2
- private const val INVERT = 3
- private const val RESET = 4
+ private const val RESET = 3
// Colors taken from https://material.io/design/color -> 2014 Material Design color palettes
@@ -465,15 +476,35 @@
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val context = preferenceManager.context
val screen = preferenceManager.createPreferenceScreen(context)
+
+ val light = PreferenceCategory(context).apply {
+ title = "Light colors"
+ screen.addPreference(this)
+ }
// Create new ColorPalette to resolve defaults
- ColorPalette().forEachColorProperty { name, color ->
+ lightColorPalette().forEachColorProperty { name, color ->
val preference = EditTextPreference(context)
- preference.key = name
+ preference.key = name + true
preference.title = name
// set the default value to be the default for ColorPalette
preference.setDefaultValue(Integer.toHexString(color.toArgb()))
preference.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
- screen.addPreference(preference)
+ light.addPreference(preference)
+ }
+
+ val dark = PreferenceCategory(context).apply {
+ title = "Dark colors"
+ screen.addPreference(this)
+ }
+
+ darkColorPalette().forEachColorProperty { name, color ->
+ val preference = EditTextPreference(context)
+ preference.key = name + false
+ preference.title = name
+ // set the default value to be the default for ColorPalette
+ preference.setDefaultValue(Integer.toHexString(color.toArgb()))
+ preference.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
+ dark.addPreference(preference)
}
preferenceScreen = screen
}
@@ -490,7 +521,7 @@
private fun ColorPalette.forEachColorProperty(action: (name: String, color: Color) -> Unit) {
ColorPalette::class.memberProperties.forEach { property ->
val name = property.name
- val color = property.get(this) as Color
+ val color = property.get(this) as? Color ?: return@forEach
action(name, color)
}
-}
\ No newline at end of file
+}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
index 5fd2c06..109a5e4 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionControlsActivity.kt
@@ -20,7 +20,6 @@
import androidx.compose.unaryPlus
import androidx.ui.core.Text
import androidx.ui.core.dp
-import androidx.ui.graphics.Color
import androidx.ui.layout.Column
import androidx.ui.layout.EdgeInsets
import androidx.ui.layout.Padding
@@ -30,7 +29,6 @@
import androidx.ui.material.samples.RadioButtonSample
import androidx.ui.material.samples.SwitchSample
import androidx.ui.material.samples.TriStateCheckboxSample
-import androidx.ui.material.surface.Surface
class SelectionControlsActivity : MaterialDemoActivity() {
@@ -39,29 +37,27 @@
val headerStyle = (+MaterialTheme.typography()).h6
val padding = EdgeInsets(10.dp)
- Surface(color = Color.White) {
- Padding(padding = padding) {
- Column {
- Text(text = "Checkbox", style = headerStyle)
- Padding(padding = padding) {
- TriStateCheckboxSample()
- }
- Text(text = "Switch", style = headerStyle)
- Padding(padding = padding) {
- SwitchSample()
- }
- Text(text = "RadioButton", style = headerStyle)
- Padding(padding = padding) {
- RadioButtonSample()
- }
- Text(text = "Radio group :: Default usage", style = headerStyle)
- Padding(padding = padding) {
- DefaultRadioGroupSample()
- }
- Text(text = "Radio group :: Custom usage", style = headerStyle)
- Padding(padding = padding) {
- CustomRadioGroupSample()
- }
+ Padding(padding = padding) {
+ Column {
+ Text(text = "Checkbox", style = headerStyle)
+ Padding(padding = padding) {
+ TriStateCheckboxSample()
+ }
+ Text(text = "Switch", style = headerStyle)
+ Padding(padding = padding) {
+ SwitchSample()
+ }
+ Text(text = "RadioButton", style = headerStyle)
+ Padding(padding = padding) {
+ RadioButtonSample()
+ }
+ Text(text = "Radio group :: Default usage", style = headerStyle)
+ Padding(padding = padding) {
+ DefaultRadioGroupSample()
+ }
+ Text(text = "Radio group :: Custom usage", style = headerStyle)
+ Padding(padding = padding) {
+ CustomRadioGroupSample()
}
}
}
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SliderActivity.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SliderActivity.kt
index 003923b..00327259 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SliderActivity.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SliderActivity.kt
@@ -18,25 +18,21 @@
import androidx.compose.Composable
import androidx.ui.core.dp
-import androidx.ui.graphics.Color
import androidx.ui.layout.Column
import androidx.ui.layout.EdgeInsets
import androidx.ui.layout.Padding
import androidx.ui.material.samples.SliderSample
import androidx.ui.material.samples.StepsSliderSample
-import androidx.ui.material.surface.Surface
class SliderActivity : MaterialDemoActivity() {
@Composable
override fun materialContent() {
val padding = EdgeInsets(10.dp)
- Surface(color = Color.White) {
- Padding(padding = padding) {
- Column {
- SliderSample()
- StepsSliderSample()
- }
+ Padding(padding = padding) {
+ Column {
+ SliderSample()
+ StepsSliderSample()
}
}
}
diff --git a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
index ff10252..4752bd1 100644
--- a/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
+++ b/ui/ui-material/integration-tests/material-studies/src/main/java/androidx/ui/material/studies/rally/RallyTheme.kt
@@ -21,9 +21,9 @@
import androidx.ui.core.em
import androidx.ui.core.sp
import androidx.ui.graphics.Color
-import androidx.ui.material.ColorPalette
import androidx.ui.material.MaterialTheme
import androidx.ui.material.Typography
+import androidx.ui.material.lightColorPalette
import androidx.ui.text.TextStyle
import androidx.ui.text.font.FontFamily
import androidx.ui.text.font.FontWeight
@@ -37,7 +37,7 @@
@Composable
fun RallyTheme(children: @Composable() () -> Unit) {
- val colors = ColorPalette(
+ val colors = lightColorPalette(
primary = rallyGreen,
surface = Color(0xFF26282F),
onSurface = Color.White,
@@ -91,7 +91,7 @@
@Composable
fun RallyDialogThemeOverlay(children: @Composable() () -> Unit) {
- val dialogColors = ColorPalette(
+ val dialogColors = lightColorPalette(
primary = Color.White,
surface = Color(0xFF1E1E1E),
onSurface = Color.White
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
index 2a3412f..0b66644 100644
--- a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
@@ -22,11 +22,13 @@
import androidx.ui.core.Text
import androidx.ui.core.sp
import androidx.ui.foundation.ColoredRect
+import androidx.ui.foundation.isSystemInDarkTheme
import androidx.ui.graphics.Color
-import androidx.ui.material.ColorPalette
import androidx.ui.material.FloatingActionButton
import androidx.ui.material.MaterialTheme
import androidx.ui.material.Typography
+import androidx.ui.material.darkColorPalette
+import androidx.ui.material.lightColorPalette
import androidx.ui.text.TextStyle
import androidx.ui.text.font.FontFamily
import androidx.ui.text.font.FontWeight
@@ -34,12 +36,16 @@
@Sampled
@Composable
fun MaterialThemeSample() {
- val colors = ColorPalette(
- primary = Color(0xFF1EB980),
- surface = Color(0xFF26282F),
- onSurface = Color.White
+ val lightColors = lightColorPalette(
+ primary = Color(0xFF1EB980)
)
+ val darkColors = darkColorPalette(
+ primary = Color(0xFF66ffc7)
+ )
+
+ val colors = if (+isSystemInDarkTheme()) darkColors else lightColors
+
val typography = Typography(
h1 = TextStyle(fontFamily = FontFamily("RobotoCondensed"),
fontWeight = FontWeight.W100,
@@ -47,11 +53,11 @@
button = TextStyle(fontFamily = FontFamily("RobotoCondensed"),
fontWeight = FontWeight.W600,
fontSize = 14.sp)
-
)
MaterialTheme(colors = colors, typography = typography) {
- FloatingActionButton("FAB with text style and color from theme", onClick = {})
+ val currentTheme = if ((+MaterialTheme.colors()).isLight) "light" else "dark"
+ FloatingActionButton("FAB with text style and color from $currentTheme theme", onClick = {})
}
}
@@ -65,5 +71,6 @@
@Sampled
@Composable
fun ThemeTextStyleSample() {
- Text(text = "H4 styled text", style = (+MaterialTheme.typography()).h4)
+ val typography = +MaterialTheme.typography()
+ Text(text = "H4 styled text", style = typography.h4)
}
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt
index b973eea..abd4b27 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/TextColorsTest.kt
@@ -36,7 +36,7 @@
@Test
fun textColorForBackgroundUsesCorrectValues() {
- val colors = ColorPalette(
+ val colors = lightColorPalette(
primary = Color(0),
onPrimary = Color(1),
secondary = Color(2),
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Color.kt b/ui/ui-material/src/main/java/androidx/ui/material/Color.kt
index f3fa80c..ee7eb9d 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Color.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Color.kt
@@ -27,6 +27,9 @@
/**
* Collection of colors in the [Material color specification]
* [https://material.io/design/color/the-color-system.html#color-theme-creation].
+ *
+ * To create a light set of colors, use [lightColorPalette]
+ * To create a dark set of colors, use [darkColorPalette]
*/
interface ColorPalette {
/**
@@ -86,13 +89,22 @@
* Color used for text and icons displayed on top of the error color.
*/
val onError: Color
+ /**
+ * Whether this ColorPalette is considered as a 'light' or 'dark' set of colors. This affects
+ * default behavior for some components: for example, in a light theme a [TopAppBar] will use
+ * [primary] by default for its background color, when in a dark theme it will use [surface].
+ */
+ val isLight: Boolean
}
/**
* Creates a complete color definition for the [Material color specification]
- * [https://material.io/design/color/the-color-system.html#color-theme-creation].
+ * [https://material.io/design/color/the-color-system.html#color-theme-creation] using the default
+ * light theme values.
+ *
+ * @see darkColorPalette
*/
-fun ColorPalette(
+fun lightColorPalette(
primary: Color = Color(0xFF6200EE),
primaryVariant: Color = Color(0xFF3700B3),
secondary: Color = Color(0xFF03DAC6),
@@ -117,7 +129,45 @@
onSecondary,
onBackground,
onSurface,
- onError
+ onError,
+ true
+)
+
+/**
+ * Creates a complete color definition for the [Material color specification]
+ * [https://material.io/design/color/the-color-system.html#color-theme-creation] using the default
+ * dark theme values.
+ *
+ * @see lightColorPalette
+ */
+fun darkColorPalette(
+ primary: Color = Color(0xFFBB86FC),
+ primaryVariant: Color = Color(0xFF3700B3),
+ secondary: Color = Color(0xFF03DAC6),
+ background: Color = Color(0xFF121212),
+ surface: Color = Color(0xFF121212),
+ error: Color = Color(0xFFCF6679),
+ onPrimary: Color = Color.Black,
+ onSecondary: Color = Color.Black,
+ onBackground: Color = Color.White,
+ onSurface: Color = Color.White,
+ onError: Color = Color.Black
+): ColorPalette = ObservableColorPalette(
+ primary,
+ primaryVariant,
+ secondary,
+ // Secondary and secondary variant are the same in dark mode, as contrast should be
+ // higher so there is no need for the variant.
+ secondary,
+ background,
+ surface,
+ error,
+ onPrimary,
+ onSecondary,
+ onBackground,
+ onSurface,
+ onError,
+ false
)
/**
@@ -146,7 +196,8 @@
onSecondary: Color,
onBackground: Color,
onSurface: Color,
- onError: Color
+ onError: Color,
+ isLight: Boolean
) : ColorPalette {
constructor(colorPalette: ColorPalette) : this(
@@ -161,7 +212,8 @@
onSecondary = colorPalette.onSecondary,
onBackground = colorPalette.onBackground,
onSurface = colorPalette.onSurface,
- onError = colorPalette.onError
+ onError = colorPalette.onError,
+ isLight = colorPalette.isLight
)
override var primary by ObservableColor(primary)
@@ -176,6 +228,7 @@
override var onBackground by ObservableColor(onBackground)
override var onSurface by ObservableColor(onSurface)
override var onError by ObservableColor(onError)
+ override var isLight by ObservableBoolean(isLight)
}
@Model
@@ -187,6 +240,15 @@
}
}
+@Model
+private class ObservableBoolean(var boolean: Boolean) {
+ operator fun getValue(thisObj: Any?, property: KProperty<*>) = boolean
+
+ operator fun setValue(thisObj: Any?, property: KProperty<*>, next: Boolean) {
+ if (boolean != next) boolean = next
+ }
+}
+
/**
* Updates the internal values of the given [ObservableColorPalette] with values from the [other]
* [ColorPalette].
@@ -204,6 +266,7 @@
onBackground = other.onBackground
onSurface = other.onSurface
onError = other.onError
+ isLight = other.isLight
return this
}
@@ -215,9 +278,7 @@
internal fun ProvideColorPalette(colorPalette: ColorPalette, children: @Composable() () -> Unit) {
val palette = when (colorPalette) {
is ObservableColorPalette -> {
- (+memo<ObservableColorPalette> {
- ObservableColorPalette(colorPalette)
- }).updateColorsFrom(colorPalette)
+ (+memo { ObservableColorPalette(colorPalette) }).updateColorsFrom(colorPalette)
}
else -> colorPalette
}
@@ -229,4 +290,4 @@
*
* To retrieve the current value of this ambient, use [MaterialTheme.colors].
*/
-internal val ColorAmbient = Ambient.of { ColorPalette() }
+internal val ColorAmbient = Ambient.of { lightColorPalette() }
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
index eeccfeb..0bc689d 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
@@ -41,7 +41,7 @@
*/
@Composable
fun MaterialTheme(
- colors: ColorPalette = ColorPalette(),
+ colors: ColorPalette = lightColorPalette(),
typography: Typography = Typography(),
children: @Composable() () -> Unit
) {
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/SemanticsTreeNodeImpl.kt b/ui/ui-platform/src/main/java/androidx/ui/core/SemanticsTreeNodeImpl.kt
index 66344f6..a8f9765 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/SemanticsTreeNodeImpl.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/SemanticsTreeNodeImpl.kt
@@ -81,7 +81,7 @@
)
parent?.addChild(wrapper)
nodes.add(wrapper)
- currentParent = parent
+ currentParent = wrapper
}
currentNode.visitChildren {
diff --git a/ui/ui-test/api/0.1.0-dev03.txt b/ui/ui-test/api/0.1.0-dev03.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/0.1.0-dev03.txt
+++ b/ui/ui-test/api/0.1.0-dev03.txt
@@ -4,6 +4,7 @@
public final class ActionsKt {
ctor public ActionsKt();
method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
method public static boolean waitForIdleCompose();
}
@@ -140,6 +141,20 @@
method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
}
+ public final class GestureScope {
+ }
+
+ public final class GestureScopeKt {
+ ctor public GestureScopeKt();
+ method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+ method public static void sendClick(androidx.ui.test.GestureScope);
+ method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+ method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+ method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+ method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+ method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+ }
+
public final class GoldenSemanticsKt {
ctor public GoldenSemanticsKt();
method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/api/current.txt b/ui/ui-test/api/current.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/current.txt
+++ b/ui/ui-test/api/current.txt
@@ -4,6 +4,7 @@
public final class ActionsKt {
ctor public ActionsKt();
method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
method public static boolean waitForIdleCompose();
}
@@ -140,6 +141,20 @@
method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
}
+ public final class GestureScope {
+ }
+
+ public final class GestureScopeKt {
+ ctor public GestureScopeKt();
+ method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+ method public static void sendClick(androidx.ui.test.GestureScope);
+ method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+ method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+ method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+ method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+ method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+ }
+
public final class GoldenSemanticsKt {
ctor public GoldenSemanticsKt();
method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-test/api/public_plus_experimental_0.1.0-dev03.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/public_plus_experimental_0.1.0-dev03.txt
+++ b/ui/ui-test/api/public_plus_experimental_0.1.0-dev03.txt
@@ -4,6 +4,7 @@
public final class ActionsKt {
ctor public ActionsKt();
method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
method public static boolean waitForIdleCompose();
}
@@ -140,6 +141,20 @@
method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
}
+ public final class GestureScope {
+ }
+
+ public final class GestureScopeKt {
+ ctor public GestureScopeKt();
+ method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+ method public static void sendClick(androidx.ui.test.GestureScope);
+ method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+ method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+ method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+ method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+ method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+ }
+
public final class GoldenSemanticsKt {
ctor public GoldenSemanticsKt();
method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/api/public_plus_experimental_current.txt b/ui/ui-test/api/public_plus_experimental_current.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/ui/ui-test/api/public_plus_experimental_current.txt
@@ -4,6 +4,7 @@
public final class ActionsKt {
ctor public ActionsKt();
method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
method public static boolean waitForIdleCompose();
}
@@ -140,6 +141,20 @@
method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
}
+ public final class GestureScope {
+ }
+
+ public final class GestureScopeKt {
+ ctor public GestureScopeKt();
+ method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+ method public static void sendClick(androidx.ui.test.GestureScope);
+ method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+ method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+ method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+ method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+ method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+ }
+
public final class GoldenSemanticsKt {
ctor public GoldenSemanticsKt();
method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/api/restricted_0.1.0-dev03.txt b/ui/ui-test/api/restricted_0.1.0-dev03.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/restricted_0.1.0-dev03.txt
+++ b/ui/ui-test/api/restricted_0.1.0-dev03.txt
@@ -4,6 +4,7 @@
public final class ActionsKt {
ctor public ActionsKt();
method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
method public static boolean waitForIdleCompose();
}
@@ -140,6 +141,20 @@
method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
}
+ public final class GestureScope {
+ }
+
+ public final class GestureScopeKt {
+ ctor public GestureScopeKt();
+ method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+ method public static void sendClick(androidx.ui.test.GestureScope);
+ method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+ method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+ method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+ method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+ method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+ }
+
public final class GoldenSemanticsKt {
ctor public GoldenSemanticsKt();
method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/api/restricted_current.txt b/ui/ui-test/api/restricted_current.txt
index 0aaf4a0..0d112cd 100644
--- a/ui/ui-test/api/restricted_current.txt
+++ b/ui/ui-test/api/restricted_current.txt
@@ -4,6 +4,7 @@
public final class ActionsKt {
ctor public ActionsKt();
method public static androidx.ui.test.SemanticsNodeInteraction doClick(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction doGesture(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.test.GestureScope,kotlin.Unit> block);
method public static androidx.ui.test.SemanticsNodeInteraction doScrollTo(androidx.ui.test.SemanticsNodeInteraction);
method public static boolean waitForIdleCompose();
}
@@ -140,6 +141,20 @@
method public static androidx.ui.test.SemanticsNodeInteraction findByText(String text, boolean ignoreCase = false);
}
+ public final class GestureScope {
+ }
+
+ public final class GestureScopeKt {
+ ctor public GestureScopeKt();
+ method public static void sendClick(androidx.ui.test.GestureScope, float x, float y);
+ method public static void sendClick(androidx.ui.test.GestureScope);
+ method public static void sendSwipe(androidx.ui.test.GestureScope, float x0, float y0, float x1, float y1, androidx.ui.core.Duration duration = 200.milliseconds);
+ method public static void sendSwipeDown(androidx.ui.test.GestureScope);
+ method public static void sendSwipeLeft(androidx.ui.test.GestureScope);
+ method public static void sendSwipeRight(androidx.ui.test.GestureScope);
+ method public static void sendSwipeUp(androidx.ui.test.GestureScope);
+ }
+
public final class GoldenSemanticsKt {
ctor public GoldenSemanticsKt();
method public static void assertEquals(androidx.ui.core.semantics.SemanticsConfiguration, androidx.ui.core.semantics.SemanticsConfiguration expected);
diff --git a/ui/ui-test/build.gradle b/ui/ui-test/build.gradle
index 78872d5..b4ccacaf 100644
--- a/ui/ui-test/build.gradle
+++ b/ui/ui-test/build.gradle
@@ -48,7 +48,7 @@
testImplementation(TRUTH)
androidTestImplementation(TRUTH)
-
+ androidTestImplementation project(":ui:ui-core")
androidTestImplementation project(':ui:ui-material')
}
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/PointerInputRecorder.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/PointerInputRecorder.kt
new file mode 100644
index 0000000..c274abb
--- /dev/null
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/PointerInputRecorder.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019 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.ui.test
+
+import androidx.ui.core.IntPxSize
+import androidx.ui.core.PointerEventPass
+import androidx.ui.core.PointerInputChange
+import androidx.ui.core.PointerInputData
+import com.google.common.truth.Truth
+
+class PointerInputRecorder {
+
+ data class DataPoint(val id: Int, val data: PointerInputData) {
+ val timestamp get() = data.uptime
+ val position get() = data.position
+ val down get() = data.down
+ }
+
+ private val _events = mutableListOf<DataPoint>()
+ val events get() = _events as List<DataPoint>
+
+ fun onPointerInput(
+ changes: List<PointerInputChange>,
+ pass: PointerEventPass,
+ @Suppress("UNUSED_PARAMETER") bounds: IntPxSize
+ ): List<PointerInputChange> {
+ if (pass == PointerEventPass.InitialDown) {
+ changes.forEach {
+ _events.add(DataPoint(it.id, it.current))
+ }
+ }
+ return changes
+ }
+
+ fun assertTimestampsAreIncreasing() {
+ events.reduce { prev, curr ->
+ Truth.assertThat(curr.timestamp).isAtLeast(prev.timestamp)
+ curr
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/androidTest/java/androidx/ui/test/SendSwipeTest.kt b/ui/ui-test/src/androidTest/java/androidx/ui/test/SendSwipeTest.kt
new file mode 100644
index 0000000..f16eb24
--- /dev/null
+++ b/ui/ui-test/src/androidTest/java/androidx/ui/test/SendSwipeTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2019 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.ui.test
+
+import androidx.compose.Composable
+import androidx.test.filters.MediumTest
+import androidx.ui.core.Alignment
+import androidx.ui.core.PointerInputWrapper
+import androidx.ui.core.TestTag
+import androidx.ui.core.dp
+import androidx.ui.foundation.shape.DrawShape
+import androidx.ui.foundation.shape.RectangleShape
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Align
+import androidx.ui.layout.Container
+import androidx.ui.semantics.Semantics
+import com.google.common.collect.Ordering
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@MediumTest
+@RunWith(JUnit4::class)
+class SendSwipeTest {
+
+ private val tag = "widget"
+
+ @get:Rule
+ val composeTestRule = createComposeRule(disableTransitions = true)
+
+ private lateinit var recorder: PointerInputRecorder
+
+ @Before
+ fun setup() {
+ recorder = PointerInputRecorder()
+ }
+
+ @Composable
+ fun Ui(alignment: Alignment) {
+ PointerInputWrapper(pointerInputHandler = recorder::onPointerInput) {
+ Align(alignment = alignment) {
+ TestTag(tag) {
+ Semantics {
+ Container(width = 100.dp, height = 100.dp) {
+ DrawShape(RectangleShape, Color.Yellow)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun swipeUp() {
+ composeTestRule.setContent { Ui(Alignment.TopLeft) }
+ findByTag(tag).doGesture { sendSwipeUp() }
+ recorder.run {
+ assertTimestampsAreIncreasing()
+ assertOnlyLastEventIsUp()
+ assertSwipeIsUp()
+ }
+ }
+
+ @Test
+ fun swipeDown() {
+ composeTestRule.setContent { Ui(Alignment.TopRight) }
+ findByTag(tag).doGesture { sendSwipeDown() }
+ recorder.run {
+ assertTimestampsAreIncreasing()
+ assertOnlyLastEventIsUp()
+ assertSwipeIsDown()
+ }
+ }
+
+ @Test
+ fun swipeLeft() {
+ composeTestRule.setContent { Ui(Alignment.BottomRight) }
+ findByTag(tag).doGesture { sendSwipeLeft() }
+ recorder.run {
+ assertTimestampsAreIncreasing()
+ assertOnlyLastEventIsUp()
+ assertSwipeIsLeft()
+ }
+ }
+
+ @Test
+ fun swipeRight() {
+ composeTestRule.setContent { Ui(Alignment.BottomLeft) }
+ findByTag(tag).doGesture { sendSwipeRight() }
+ recorder.run {
+ assertTimestampsAreIncreasing()
+ assertOnlyLastEventIsUp()
+ assertSwipeIsRight()
+ }
+ }
+}
+
+private fun PointerInputRecorder.assertOnlyLastEventIsUp() {
+ assertThat(events.last().down).isFalse()
+ assertThat(events.filter { !it.down }.size).isEqualTo(1)
+}
+
+private fun PointerInputRecorder.assertSwipeIsUp() {
+ // Must have at least two events to have a direction
+ assertThat(events.size).isAtLeast(2)
+ // Last event must be above first event
+ assertThat(events.last().position!!.y).isLessThan(events.first().position!!.y)
+ // All events in between only move up
+ events.map { it.position!!.x.value }.assertSame(tolerance = 0.001f)
+ events.map { it.position!!.y }.assertDecreasing()
+}
+
+private fun PointerInputRecorder.assertSwipeIsDown() {
+ // Must have at least two events to have a direction
+ assertThat(events.size).isAtLeast(2)
+ // Last event must be below first event
+ assertThat(events.last().position!!.y).isGreaterThan(events.first().position!!.y)
+ // All events in between only move down
+ events.map { it.position!!.x.value }.assertSame(tolerance = 0.001f)
+ events.map { it.position!!.y }.assertIncreasing()
+}
+
+private fun PointerInputRecorder.assertSwipeIsLeft() {
+ // Must have at least two events to have a direction
+ assertThat(events.size).isAtLeast(2)
+ // Last event must be to the left of first event
+ assertThat(events.last().position!!.x).isLessThan(events.first().position!!.x)
+ // All events in between only move to the left
+ events.map { it.position!!.x }.assertDecreasing()
+ events.map { it.position!!.y.value }.assertSame(tolerance = 0.001f)
+}
+
+private fun PointerInputRecorder.assertSwipeIsRight() {
+ // Must have at least two events to have a direction
+ assertThat(events.size).isAtLeast(2)
+ // Last event must be to the right of first event
+ assertThat(events.last().position!!.x).isGreaterThan(events.first().position!!.x)
+ // All events in between only move to the right
+ events.map { it.position!!.x }.assertIncreasing()
+ events.map { it.position!!.y.value }.assertSame(tolerance = 0.001f)
+}
+
+private fun List<Float>.assertSame(tolerance: Float = 0f) {
+ if (size <= 1) {
+ return
+ }
+ val baseValue = first()
+ assertThat(min()).isWithin(tolerance).of(baseValue)
+ assertThat(max()).isWithin(tolerance).of(baseValue)
+}
+
+private fun <E : Comparable<E>> List<E>.assertIncreasing() {
+ assertThat(this).isInOrder(Ordering.natural<E>())
+}
+
+private fun <E : Comparable<E>> List<E>.assertDecreasing() {
+ assertThat(this).isInOrder(Ordering.natural<E>().reverse<E>())
+}
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt b/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt
index 86e74da..109498b 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt
@@ -27,18 +27,17 @@
* Performs a click action on the given component.
*/
fun SemanticsNodeInteraction.doClick(): SemanticsNodeInteraction {
- // TODO(b/129400818): uncomment this after Merge Semantics is merged
- // assertHasClickAction()
-
- // TODO(catalintudor): get real coordinates after Semantics API is ready (b/125702443)
- val globalRect = semanticsTreeNode.globalRect
- ?: throw AssertionError("Semantic Node has no child layout to perform click on!")
- val x = globalRect.left + 1f
- val y = globalRect.top + 1f
-
- semanticsTreeInteraction.sendClick(x, y)
-
- return this
+ // TODO(jellefresen): Replace with semantics action when semantics merging is done
+ // The problem we currently have is that the click action might be defined on a different
+ // semantics node than we're interacting with now, even though it is "semantically" the same.
+ // E.g., findByText(buttonText) finds the Text's semantics node, but the click action is
+ // defined on the wrapping Button's semantics node.
+ // Since in general the intended click action can be on a wrapping node or a child node, we
+ // can't just forward to the correct node, as we don't know if we should search up or down the
+ // tree.
+ return doGesture {
+ sendClick()
+ }
}
/**
@@ -85,4 +84,21 @@
return this
}
+/**
+ * Executes the gestures specified in the given block.
+ *
+ * Example usage:
+ * findByTag("myWidget")
+ * .doGesture {
+ * sendSwipeUp()
+ * }
+ */
+fun SemanticsNodeInteraction.doGesture(
+ block: GestureScope.() -> Unit
+): SemanticsNodeInteraction {
+ val scope = GestureScope(this)
+ scope.block()
+ return this
+}
+
fun waitForIdleCompose(): Boolean = semanticsTreeInteractionFactory({ true }).waitForIdleCompose()
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt b/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt
new file mode 100644
index 0000000..3d5daf2
--- /dev/null
+++ b/ui/ui-test/src/main/java/androidx/ui/test/GestureScope.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2019 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.ui.test
+
+import androidx.ui.core.Duration
+import androidx.ui.core.SemanticsTreeNode
+import androidx.ui.core.milliseconds
+
+/**
+ * An object that has an associated component in which one can inject gestures. The gestures can
+ * be injected by calling methods defined on [GestureScope], such as [sendSwipeUp]. The associated
+ * component is the [SemanticsTreeNode] found by one of the finder methods such as [findByTag].
+ *
+ * Example usage:
+ * findByTag("myWidget")
+ * .doGesture {
+ * sendSwipeUp()
+ * }
+ */
+class GestureScope internal constructor(
+ internal val semanticsNodeInteraction: SemanticsNodeInteraction
+) {
+ internal inline val semanticsTreeNode
+ get() = semanticsNodeInteraction.semanticsTreeNode
+ internal inline val semanticsTreeInteraction
+ get() = semanticsNodeInteraction.semanticsTreeInteraction
+}
+
+/**
+ * The distance of a swipe's start position from the node's edge, in terms of the node's length.
+ * We do not start the swipe exactly on the node's edge, but somewhat more inward, since swiping
+ * from the exact edge may behave in an unexpected way (e.g. may open a navigation drawer).
+ */
+private const val edgeFuzzFactor = 0.083f
+
+/**
+ * Performs a click gesture on the given coordinate on the associated component. The coordinate
+ * ([x], [y]) is in the component's local coordinate system.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendClick(x: Float, y: Float) {
+ val globalRect = semanticsTreeNode.globalRect
+ ?: throw AssertionError("Semantic Node has no child layout to perform click on!")
+ val xOffset = globalRect.left
+ val yOffset = globalRect.top
+
+ semanticsTreeInteraction.sendInput {
+ it.sendClick(x + xOffset, y + yOffset)
+ }
+}
+
+/**
+ * Performs a click gesture on the associated component. The click is done in the middle of the
+ * component's bounds.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendClick() {
+ val globalRect = semanticsTreeNode.globalRect
+ ?: throw AssertionError("Semantic Node has no child layout to perform click on!")
+ val x = globalRect.width / 2
+ val y = globalRect.height / 2
+
+ sendClick(x, y)
+}
+
+/**
+ * Performs the swipe gesture on the associated component. The MotionEvents are linearly
+ * interpolated between ([x0], [y0]) and ([x1], [y1]). The coordinates are in the component's local
+ * coordinate system, i.e. (0, 0) is the top left corner of the component. The default duration is
+ * 200 milliseconds.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendSwipe(
+ x0: Float,
+ y0: Float,
+ x1: Float,
+ y1: Float,
+ duration: Duration = 200.milliseconds
+) {
+ val globalRect = semanticsTreeNode.globalRect
+ ?: throw AssertionError("Semantic Node has no child layout to perform swipe on!")
+ val xOffset = globalRect.left
+ val yOffset = globalRect.top
+
+ semanticsTreeInteraction.sendInput {
+ it.sendSwipe(x0 + xOffset, y0 + yOffset, x1 + xOffset, y1 + yOffset, duration)
+ }
+}
+
+/**
+ * Performs a swipe up gesture on the associated component. The gesture starts slightly above the
+ * bottom of the component and ends at the top.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendSwipeUp() {
+ val globalRect = semanticsTreeNode.globalRect
+ ?: throw AssertionError("Semantic Node has no child layout to perform swipe on!")
+ val x = globalRect.width / 2
+ val y0 = globalRect.height * (1 - edgeFuzzFactor)
+ val y1 = 0f
+
+ sendSwipe(x, y0, x, y1, 200.milliseconds)
+}
+
+/**
+ * Performs a swipe down gesture on the associated component. The gesture starts slightly below the
+ * top of the component and ends at the bottom.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendSwipeDown() {
+ val globalRect = semanticsTreeNode.globalRect
+ ?: throw AssertionError("Semantic Node has no child layout to perform swipe on!")
+ val x = globalRect.width / 2
+ val y0 = globalRect.height * edgeFuzzFactor
+ val y1 = globalRect.height
+
+ sendSwipe(x, y0, x, y1, 200.milliseconds)
+}
+
+/**
+ * Performs a swipe left gesture on the associated component. The gesture starts slightly left of
+ * the right side of the component and ends at the left side.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendSwipeLeft() {
+ val globalRect = semanticsTreeNode.globalRect
+ ?: throw AssertionError("Semantic Node has no child layout to perform swipe on!")
+ val x0 = globalRect.width * (1 - edgeFuzzFactor)
+ val x1 = 0f
+ val y = globalRect.height / 2
+
+ sendSwipe(x0, y, x1, y, 200.milliseconds)
+}
+
+/**
+ * Performs a swipe right gesture on the associated component. The gesture starts slightly right of
+ * the left side of the component and ends at the right side.
+ *
+ * Throws [AssertionError] when the component doesn't have a bounding rectangle set
+ */
+fun GestureScope.sendSwipeRight() {
+ val globalRect = semanticsTreeNode.globalRect
+ ?: throw AssertionError("Semantic Node has no child layout to perform swipe on!")
+ val x0 = globalRect.width * edgeFuzzFactor
+ val x1 = globalRect.width
+ val y = globalRect.height / 2
+
+ sendSwipe(x0, y, x1, y, 200.milliseconds)
+}
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt b/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt
new file mode 100644
index 0000000..c8b061a
--- /dev/null
+++ b/ui/ui-test/src/main/java/androidx/ui/test/InputDispatcher.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 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.ui.test
+
+import androidx.ui.core.Duration
+
+internal interface InputDispatcher {
+ /**
+ * Sends a click event at coordinate ([x], [y]). There will be 10ms in between the down and
+ * the up event. This method blocks until all input events have been dispatched.
+ */
+ fun sendClick(x: Float, y: Float)
+
+ /**
+ * Sends a swipe gesture from ([x0], [y0]) to ([x1], [y1]) with the given [duration]. This
+ * method blocks until all input events have been dispatched.
+ */
+ fun sendSwipe(x0: Float, y0: Float, x1: Float, y1: Float, duration: Duration)
+}
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/SemanticsTreeInteraction.kt b/ui/ui-test/src/main/java/androidx/ui/test/SemanticsTreeInteraction.kt
index 94a780f..d51eb8a 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/SemanticsTreeInteraction.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/SemanticsTreeInteraction.kt
@@ -26,6 +26,7 @@
* extension functions. This class is expected to have Android and host side specific
* implementations.
*/
+// TODO(jellefresen): Convert to interface, no need for an abstract class
internal abstract class SemanticsTreeInteraction {
internal abstract fun findAllMatching(): List<SemanticsNodeInteraction>
@@ -34,7 +35,7 @@
internal abstract fun performAction(action: (SemanticsTreeProvider) -> Unit)
- internal abstract fun sendClick(x: Float, y: Float)
+ internal abstract fun sendInput(action: (InputDispatcher) -> Unit)
internal abstract fun contains(semanticsConfiguration: SemanticsConfiguration): Boolean
@@ -58,4 +59,4 @@
* just wait for longer timeouts as the tests might still have a value.
*/
// TODO(pavlis): Turn this on by default for all our Compose tests
-internal var throwOnRecomposeTimeout = false
\ No newline at end of file
+internal var throwOnRecomposeTimeout = false
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidInputDispatcher.kt b/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidInputDispatcher.kt
new file mode 100644
index 0000000..d7ab848
--- /dev/null
+++ b/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidInputDispatcher.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 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.ui.test.android
+
+import android.os.Handler
+import android.os.Looper
+import android.os.SystemClock
+import android.view.MotionEvent
+import androidx.ui.core.Duration
+import androidx.ui.core.SemanticsTreeProvider
+import androidx.ui.core.inMilliseconds
+import androidx.ui.core.milliseconds
+import androidx.ui.lerp
+import androidx.ui.test.InputDispatcher
+import java.util.concurrent.CountDownLatch
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+internal class AndroidInputDispatcher(
+ private val treeProvider: SemanticsTreeProvider
+) : InputDispatcher {
+ /**
+ * The minimum time between two successive injected MotionEvents. Ideally, the value should
+ * reflect a realistic pointer input sample rate, but that depends on too many factors. Instead,
+ * the value is chosen comfortably below the targeted frame rate (60 fps, equating to a 16ms
+ * period).
+ */
+ private val eventPeriod = 10.milliseconds.inMilliseconds()
+
+ private val handler = Handler(Looper.getMainLooper())
+
+ override fun sendClick(x: Float, y: Float) {
+ val downTime = SystemClock.uptimeMillis()
+ treeProvider.sendMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, x, y)
+ treeProvider.sendMotionEvent(downTime, downTime + eventPeriod, MotionEvent.ACTION_UP, x, y)
+ }
+
+ override fun sendSwipe(x0: Float, y0: Float, x1: Float, y1: Float, duration: Duration) {
+ var step = 0
+ val steps = min(1, (duration.inMilliseconds() / eventPeriod.toFloat()).roundToInt())
+ val downTime = SystemClock.uptimeMillis()
+ val upTime = downTime + duration.inMilliseconds()
+
+ treeProvider.sendMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, x0, y0)
+ while (step++ < steps) {
+ val progress = step / steps.toFloat()
+ val t = lerp(downTime, upTime, progress)
+ val x = lerp(x0, x1, progress)
+ val y = lerp(y0, y1, progress)
+ treeProvider.sendMotionEvent(downTime, t, MotionEvent.ACTION_MOVE, x, y)
+ }
+ treeProvider.sendMotionEvent(downTime, upTime, MotionEvent.ACTION_UP, x1, y1)
+ }
+
+ /**
+ * Sends an event with the given parameters. Method blocks depending on [waitUntilEventTime].
+ * @param waitUntilEventTime If `true`, blocks until [eventTime]
+ */
+ private fun SemanticsTreeProvider.sendMotionEvent(
+ downTime: Long,
+ eventTime: Long,
+ action: Int,
+ x: Float,
+ y: Float,
+ waitUntilEventTime: Boolean = true
+ ) {
+ if (waitUntilEventTime) {
+ val currTime = SystemClock.uptimeMillis()
+ if (currTime < eventTime) {
+ SystemClock.sleep(eventTime - currTime)
+ }
+ }
+ sendAndRecycleEvent(MotionEvent.obtain(downTime, eventTime, action, x, y, 0))
+ }
+
+ /**
+ * Sends the [event] to the [SemanticsTreeProvider] and [recycles][MotionEvent.recycle] it
+ * regardless of the result. This method blocks until the event is sent.
+ */
+ private fun SemanticsTreeProvider.sendAndRecycleEvent(event: MotionEvent) {
+ val latch = CountDownLatch(1)
+ handler.post {
+ try {
+ sendEvent(event)
+ } finally {
+ event.recycle()
+ latch.countDown()
+ }
+ }
+ latch.await()
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidSemanticsTreeInteraction.kt b/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidSemanticsTreeInteraction.kt
index 59feac9..370ee91 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidSemanticsTreeInteraction.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/android/AndroidSemanticsTreeInteraction.kt
@@ -22,7 +22,6 @@
import android.content.Context
import android.os.Handler
import android.os.Looper
-import android.os.SystemClock
import android.view.Choreographer
import android.view.MotionEvent
import android.view.View
@@ -38,6 +37,7 @@
import androidx.ui.core.px
import androidx.ui.core.semantics.SemanticsConfiguration
import androidx.ui.engine.geometry.Rect
+import androidx.ui.test.InputDispatcher
import androidx.ui.test.SemanticsNodeInteraction
import androidx.ui.test.SemanticsTreeInteraction
import java.util.concurrent.CountDownLatch
@@ -111,6 +111,11 @@
hadPendingChangesAfterLastAction = waitForIdleCompose()
}
+ override fun sendInput(action: (InputDispatcher) -> Unit) {
+ action(AndroidInputDispatcher(findActivityAndTreeProvider().treeProvider))
+ hadPendingChangesAfterLastAction = waitForIdleCompose()
+ }
+
/**
* Waits for Compose runtime to be idle - meaning it has no pending changes.
*
@@ -153,25 +158,6 @@
})
}
- override fun sendClick(x: Float, y: Float) {
- performAction { treeProvider ->
- val downTime = SystemClock.uptimeMillis()
- val eventDown = MotionEvent.obtain(
- downTime, downTime,
- MotionEvent.ACTION_DOWN, x, y, 0
- )
- treeProvider.sendEvent(eventDown)
- eventDown.recycle()
-
- val eventUp = MotionEvent.obtain(
- downTime, downTime + 10,
- MotionEvent.ACTION_UP, x, y, 0
- )
- treeProvider.sendEvent(eventUp)
- eventUp.recycle()
- }
- }
-
override fun contains(semanticsConfiguration: SemanticsConfiguration): Boolean {
waitForIdleCompose()
@@ -281,4 +267,4 @@
val context: Context,
val treeProvider: SemanticsTreeProvider
)
-}
\ No newline at end of file
+}
diff --git a/ui/ui-test/src/test/java/androidx/ui/test/helpers/FakeSemanticsTreeInteraction.kt b/ui/ui-test/src/test/java/androidx/ui/test/helpers/FakeSemanticsTreeInteraction.kt
index d580c4c..6525ece 100644
--- a/ui/ui-test/src/test/java/androidx/ui/test/helpers/FakeSemanticsTreeInteraction.kt
+++ b/ui/ui-test/src/test/java/androidx/ui/test/helpers/FakeSemanticsTreeInteraction.kt
@@ -20,6 +20,7 @@
import androidx.ui.core.SemanticsTreeProvider
import androidx.ui.core.semantics.SemanticsConfiguration
import androidx.ui.engine.geometry.Rect
+import androidx.ui.test.InputDispatcher
import androidx.ui.test.SemanticsNodeInteraction
import androidx.ui.test.SemanticsTreeInteraction
import androidx.ui.test.SemanticsTreeNodeStub
@@ -69,7 +70,7 @@
TODO("replace with host side interaction")
}
- override fun sendClick(x: Float, y: Float) {
+ override fun sendInput(action: (InputDispatcher) -> Unit) {
TODO("replace with host side interaction")
}
diff --git a/webkit/integration-tests/testapp/README.md b/webkit/integration-tests/testapp/README.md
index 7e48107..34e18f1 100644
--- a/webkit/integration-tests/testapp/README.md
+++ b/webkit/integration-tests/testapp/README.md
@@ -10,7 +10,7 @@
cd frameworks/support/
# Optional: you can use Android Studio as your editor
-./studiow -y
+./studiow
# Build the app
./gradlew :webkit:integration-tests:testapp:assembleDebug
diff --git a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
index a31db81..9cfd702 100644
--- a/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/webkit/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -102,5 +102,8 @@
<activity
android:name=".FullscreenActivity"
android:exported="true" />
+ <activity
+ android:name=".WebMessageListenerActivity"
+ android:exported="true" />
</application>
</manifest>
diff --git a/webkit/integration-tests/testapp/src/main/assets/www/web_message_listener.html b/webkit/integration-tests/testapp/src/main/assets/www/web_message_listener.html
new file mode 100644
index 0000000..11ed1b0
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/assets/www/web_message_listener.html
@@ -0,0 +1,83 @@
+<html>
+<!-- Copyright 2019 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.
+-->
+<head>
+ <script type="text/javascript">
+ let timestamp = 0;
+ replyObject.onmessage = function(event) {
+ document.getElementById("result").innerHTML = event.data;
+ };
+ // Send a message to app, so app could get a JsReplyProxy to reply back to JavaScript.
+ replyObject.postMessage("initialization");
+
+ const channel = new MessageChannel();
+ channel.port2.onmessage = function(event) {
+ document.getElementById("port_result").innerHTML = event.data;
+ };
+ replyWithMessagePortObject.postMessage("send port", [channel.port1]);
+
+ let messageCounter = 0;
+ multipleMessagesObject.addEventListener("message", function(event) {
+ const i = parseInt(event.data);
+ if (i < 4999) {
+ multipleMessagesObject.postMessage((i + 1).toString());
+ if (i % 100 === 0) {
+ const counterInfo = (messageCounter + i)+ " messages sent.";
+ document.getElementById("multiple_messages").innerHTML = counterInfo;
+ }
+ } else {
+ let average = (performance.now() - timestamp) / 5000;
+ let result = "Average time over 5000 messages: " + average.toFixed(2) + "ms.";
+ document.getElementById("multiple_messages").innerHTML = result;
+ messageCounter += i + 1;
+ document.getElementById("multiple_button").disabled = false;
+ }
+ });
+ function startPostMessage(e) {
+ e.preventDefault();
+ document.getElementById("multiple_button").disabled = true;
+ timestamp = performance.now();
+ multipleMessagesObject.postMessage("0");
+ }
+
+ function showToast(e) {
+ e.preventDefault();
+ const message = document.getElementById("toast_message").value;
+ toastObject.postMessage(message);
+ }
+ </script>
+</head>
+<body>
+ <h1>Web content (within WebView)</h1>
+ <div>
+ <span>Message from app: </span>
+ <span id="result" style="color:red">Not received.</span>
+ </div>
+ <div>
+ <span>Message from app (via MessagePort): </span>
+ <span id="port_result" style="color:red">Not received.</span>
+ </div>
+ <div>
+ <input value="toast!" id="toast_message">
+ <button type="button" onclick="showToast(event)">Show Toast</button>
+ </div>
+ <div>
+ <button id="multiple_button" type="button" onclick="startPostMessage(event)">
+ Send 5000 messages
+ </button>
+ <div id="multiple_messages"></div>
+ </div>
+</body>
+</html>
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java
index 7c55bc6..634da07 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/MainActivity.java
@@ -66,6 +66,9 @@
new MenuListView.MenuItem(
getResources().getString(R.string.fullscreen_activity_title),
new Intent(activityContext, FullscreenActivity.class)),
+ new MenuListView.MenuItem(
+ getResources().getString(R.string.web_message_listener_activity_title),
+ new Intent(activityContext, WebMessageListenerActivity.class)),
};
listView.setItems(menuItems);
}
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerActivity.java
new file mode 100644
index 0000000..6ebec6e
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerActivity.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2019 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 com.example.androidx.webkit;
+
+import static androidx.webkit.WebViewAssetLoader.AssetsPathHandler;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.AbsoluteSizeSpan;
+import android.view.View;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.webkit.JsReplyProxy;
+import androidx.webkit.WebMessageCompat;
+import androidx.webkit.WebMessagePortCompat;
+import androidx.webkit.WebViewAssetLoader;
+import androidx.webkit.WebViewCompat;
+import androidx.webkit.WebViewFeature;
+
+import java.util.Arrays;
+
+/**
+ * An {@link Activity} to exercise WebMessageListener related functionality.
+ */
+@SuppressLint("RestrictedApi")
+public class WebMessageListenerActivity extends AppCompatActivity {
+ private TextView mTextView;
+ private final Uri mExampleUri = new Uri.Builder()
+ .scheme("https")
+ .authority("example.com")
+ .appendPath("androidx_webkit")
+ .appendPath("example")
+ .appendPath("assets")
+ .build();
+ private Button mReplyProxyButton;
+ private Button mPortButton;
+
+ private static class MyWebViewClient extends WebViewClient {
+ private final WebViewAssetLoader mAssetLoader;
+
+ MyWebViewClient(WebViewAssetLoader loader) {
+ mAssetLoader = loader;
+ }
+
+ @Override
+ @RequiresApi(21)
+ public WebResourceResponse shouldInterceptRequest(WebView view,
+ WebResourceRequest request) {
+ return mAssetLoader.shouldInterceptRequest(request.getUrl());
+ }
+
+ @Override
+ @SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
+ public WebResourceResponse shouldInterceptRequest(WebView view, String request) {
+ return mAssetLoader.shouldInterceptRequest(Uri.parse(request));
+ }
+ }
+
+ private static class ReplyMessageListener implements WebViewCompat.WebMessageListener {
+ private final Context mContext;
+ private JsReplyProxy mReplyProxy;
+
+ ReplyMessageListener(Context context, Button button) {
+ mContext = context;
+ button.setOnClickListener((View v) -> {
+ if (mReplyProxy == null) return;
+ mReplyProxy.postMessage("ReplyProxy button clicked.");
+ });
+ }
+
+ @Override
+ public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
+ boolean isMainFrame, JsReplyProxy replyProxy) {
+ if (message.getData().equals("initialization")) {
+ mReplyProxy = replyProxy;
+ }
+ }
+ }
+
+ private static class MessagePortMessageListener implements WebViewCompat.WebMessageListener {
+ private final Context mContext;
+ private WebMessagePortCompat mPort;
+
+ MessagePortMessageListener(Context context, Button button) {
+ mContext = context;
+ button.setOnClickListener((View v) -> {
+ if (mPort == null) return;
+ mPort.postMessage(new WebMessageCompat("Port button clicked."));
+ });
+ }
+
+ @Override
+ public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
+ boolean isMainFrame, JsReplyProxy replyProxy) {
+ if (message.getData().equals("send port")) {
+ mPort = message.getPorts()[0];
+ }
+ }
+ }
+
+ private static class ToastMessageListener implements WebViewCompat.WebMessageListener {
+ private final Context mContext;
+
+ ToastMessageListener(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
+ boolean isMainFrame, JsReplyProxy replyProxy) {
+ Toast.makeText(mContext, "Toast: " + message.getData(), Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private static class MultipleMessagesListener implements WebViewCompat.WebMessageListener {
+ private final Context mContext;
+ private final TextView mTextView;
+ private int mCounter = 0;
+
+ MultipleMessagesListener(Context context, TextView textView) {
+ mContext = context;
+ mTextView = textView;
+ }
+
+ @Override
+ public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
+ boolean isMainFrame, JsReplyProxy replyProxy) {
+ replyProxy.postMessage(message.getData());
+ mCounter++;
+ if (mCounter % 100 == 0) {
+ mTextView.setText(TextUtils.concat(
+ createNativeTitle(), "\n", "" + mCounter + " messages received."));
+ }
+ }
+ }
+
+ @SuppressLint("SetJavascriptEnabled")
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_web_message_listener);
+ setTitle(R.string.web_message_listener_activity_title);
+ WebkitHelpers.appendWebViewVersionToTitle(this);
+
+ // Use WebViewAssetLoader to load html page from app's assets.
+ WebViewAssetLoader assetLoader =
+ new WebViewAssetLoader.Builder()
+ .setDomain("example.com")
+ .addPathHandler(mExampleUri.getPath() + "/", new AssetsPathHandler(this))
+ .build();
+
+ mTextView = findViewById(R.id.textview);
+ mTextView.setText(createNativeTitle());
+
+ mReplyProxyButton = findViewById(R.id.button_reply_proxy);
+ mPortButton = findViewById(R.id.button_port);
+
+ WebView webView = findViewById(R.id.webview);
+ webView.setWebViewClient(new MyWebViewClient(assetLoader));
+ webView.getSettings().setJavaScriptEnabled(true);
+
+ // Add WebMessageListeners.
+ if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
+ WebViewCompat.addWebMessageListener(webView, "replyObject",
+ Arrays.asList("https://example.com"),
+ new ReplyMessageListener(this, mReplyProxyButton));
+ WebViewCompat.addWebMessageListener(webView, "replyWithMessagePortObject",
+ Arrays.asList("https://example.com"),
+ new MessagePortMessageListener(this, mPortButton));
+ WebViewCompat.addWebMessageListener(webView, "toastObject",
+ Arrays.asList("https://example.com"), new ToastMessageListener(this));
+ WebViewCompat.addWebMessageListener(webView, "multipleMessagesObject",
+ Arrays.asList("https://example.com"),
+ new MultipleMessagesListener(this, mTextView));
+ }
+
+ webView.loadUrl(
+ Uri.withAppendedPath(mExampleUri, "www/web_message_listener.html").toString());
+ }
+
+ private static CharSequence createNativeTitle() {
+ final String title = "Native View";
+ SpannableString ss = new SpannableString(title);
+ ss.setSpan(new AbsoluteSizeSpan(55, true), 0, title.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return ss;
+ }
+}
diff --git a/webkit/integration-tests/testapp/src/main/res/layout/activity_web_message_listener.xml b/webkit/integration-tests/testapp/src/main/res/layout/activity_web_message_listener.xml
new file mode 100644
index 0000000..1c64937
--- /dev/null
+++ b/webkit/integration-tests/testapp/src/main/res/layout/activity_web_message_listener.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/activity_web_message_listener"
+ android:orientation="vertical"
+ android:weightSum="3"
+ android:background="#FF0"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <WebView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/webview"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="2"/>
+ <TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/textview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+ <Button
+ android:id="@+id/button_reply_proxy"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:text="Reply via ReplyProxy" />
+ <Button
+ android:id="@+id/button_port"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBottom="@+id/button1"
+ android:text="Reply via Port" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/webkit/integration-tests/testapp/src/main/res/values/strings.xml b/webkit/integration-tests/testapp/src/main/res/values/strings.xml
index 7ac3fe6..52ab787 100644
--- a/webkit/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/webkit/integration-tests/testapp/src/main/res/values/strings.xml
@@ -70,4 +70,5 @@
<string name="renderer_terminated_description">WebView renderer was terminated.</string>
<string name="renderer_unresponsive_description">WebView renderer has become unresponsive.</string>
<string name="fullscreen_activity_title">Fullscreen web contents Demo</string>
+ <string name="web_message_listener_activity_title">WebMessageListener Demo</string>
</resources>