Merge "Add illustration widget to SPA and create a gallary sample page for it. The widget now supports image resources."

This commit is contained in:
Kelly Zhang
2022-09-05 12:50:56 +00:00
committed by Android (Google) Code Review
11 changed files with 391 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="412dp"
android:height="300dp"
android:viewportWidth="412"
android:viewportHeight="300">
<path
android:pathData="M383.9,300H28.1C12.6,300 0,287.4 0,271.9V28.1C0,12.6 12.6,0 28.1,0h355.8C399.4,0 412,12.6 412,28.1v243.8C412,287.4 399.4,300 383.9,300z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M79.2,179.6h53.6v8.5h-53.6z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M142.5,179.6h30.4v8.5h-30.4z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M79.2,195.5h79.2v8.5h-79.2z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M168.1,195.5h34.1v8.5h-34.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M211.9,195.5h34.1v8.5h-34.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M182.7,179.6h73.1v8.5h-73.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M265.5,179.6h26.8v8.5h-26.8z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M302.1,179.6h26.8v8.5h-26.8z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M142.7,67.9h-11.5c-1.6,0 -2.9,1.3 -2.9,2.9H67.8c-7.9,0 -14.4,6.5 -14.4,14.4v132.4c0,7.9 6.5,14.4 14.4,14.4h276.4c7.9,0 14.4,-6.5 14.4,-14.4V85.2c0,-7.9 -6.5,-14.4 -14.4,-14.4H203.1c0,-1.6 -1.3,-2.9 -2.9,-2.9h-28.8c-1.6,0 -2.9,1.3 -2.9,2.9h-23C145.5,69.2 144.3,67.9 142.7,67.9zM344.2,73.7c6.4,0 11.5,5.2 11.5,11.5v132.4c0,6.3 -5.2,11.5 -11.5,11.5H67.8c-6.4,0 -11.5,-5.2 -11.5,-11.5V85.2c0,-6.3 5.2,-11.5 11.5,-11.5H344.2z"
android:fillColor="#DADCE0"/>
</vector>

View File

@@ -22,6 +22,7 @@ import com.android.settingslib.spa.framework.common.SettingsPageProviderReposito
import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
@@ -45,6 +46,7 @@ object SpaEnvironment {
SpinnerPageProvider,
SettingsPagerPageProvider,
FooterPageProvider,
IllustrationPageProvider,
),
rootPageData = listOf(
SettingsPage(HomePageProvider.name),

View File

@@ -28,6 +28,7 @@ import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SpaEnvironment
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
@@ -53,6 +54,7 @@ private fun HomePage() {
SpinnerPageProvider.EntryItem()
SettingsPagerPageProvider.EntryItem()
FooterPageProvider.EntryItem()
IllustrationPageProvider.EntryItem()
/**
* A test button to generate hierarchy.

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.widget.Illustration
import com.android.settingslib.spa.widget.IllustrationModel
import com.android.settingslib.spa.widget.ResourceType
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
object IllustrationPageProvider : SettingsPageProvider {
override val name = "Illustration"
@Composable
override fun Page(arguments: Bundle?) {
IllustrationPage()
}
@Composable
fun EntryItem() {
Preference(object : PreferenceModel {
override val title = "Sample Illustration"
override val onClick = navigator(name)
})
}
}
@Composable
private fun IllustrationPage() {
Column(Modifier.verticalScroll(rememberScrollState())) {
Preference(object : PreferenceModel {
override val title = "Image Illustration"
})
Illustration(object : IllustrationModel {
override val resId = R.drawable.accessibility_captioning_banner
override val resourceType = ResourceType.IMAGE
})
}
}
@Preview(showBackground = true)
@Composable
private fun IllustrationPagePreview() {
SettingsTheme {
IllustrationPage()
}
}

View File

@@ -66,4 +66,5 @@ dependencies {
api 'androidx.navigation:navigation-compose:2.5.0'
api 'com.google.android.material:material:1.6.1'
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
implementation 'com.airbnb.android:lottie-compose:5.2.0'
}

View File

@@ -39,4 +39,10 @@ object SettingsDimension {
/** The size when app icon is displayed in App info page. */
val appIconInfoSize = 48.dp
/** The sizes info of illustration widget. */
val illustrationMaxWidth = 412.dp
val illustrationMaxHeight = 300.dp
val illustrationPadding = 16.dp
val illustrationCornerRadius = 28.dp
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.ImageBox
enum class ResourceType { IMAGE, LOTTIE }
/**
* The widget model for [Illustration] widget.
*/
interface IllustrationModel {
/**
* The resource id of this [Illustration].
*/
val resId: Int
/**
* The resource type of the [Illustration].
*
* It should be Lottie or Image.
*/
val resourceType: ResourceType
}
/**
* Illustration widget.
*
* Data is provided through [IllustrationModel].
*/
@Composable
fun Illustration(model: IllustrationModel) {
Illustration(
resId = model.resId,
resourceType = model.resourceType,
modifier = Modifier,
)
}
@Composable
fun Illustration(
resId: Int,
resourceType: ResourceType,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(SettingsDimension.illustrationPadding),
horizontalAlignment = Alignment.CenterHorizontally,
) {
val illustrationModifier = modifier
.sizeIn(
maxWidth = SettingsDimension.illustrationMaxWidth,
maxHeight = SettingsDimension.illustrationMaxHeight,
)
.clip(RoundedCornerShape(SettingsDimension.illustrationCornerRadius))
.background(color = MaterialTheme.colorScheme.surface)
when (resourceType) {
ResourceType.LOTTIE -> {
// TODO: Add Lottie function after lottie is enabled.
}
ResourceType.IMAGE -> {
ImageBox(
resId = resId,
contentDescription = null,
modifier = illustrationModifier,
)
}
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
@Composable
fun ImageBox(
resId: Int,
contentDescription: String?,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier,
) {
Image(
painter = painterResource(resId),
contentDescription = contentDescription,
)
}
}

View File

@@ -31,6 +31,9 @@ android {
}
sourceSets {
main {
res.srcDirs = ["res"]
}
androidTest {
kotlin {
srcDir "src"

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="412dp"
android:height="300dp"
android:viewportWidth="412"
android:viewportHeight="300">
<path
android:pathData="M383.9,300H28.1C12.6,300 0,287.4 0,271.9V28.1C0,12.6 12.6,0 28.1,0h355.8C399.4,0 412,12.6 412,28.1v243.8C412,287.4 399.4,300 383.9,300z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M79.2,179.6h53.6v8.5h-53.6z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M142.5,179.6h30.4v8.5h-30.4z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M79.2,195.5h79.2v8.5h-79.2z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M168.1,195.5h34.1v8.5h-34.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M211.9,195.5h34.1v8.5h-34.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M182.7,179.6h73.1v8.5h-73.1z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M265.5,179.6h26.8v8.5h-26.8z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M302.1,179.6h26.8v8.5h-26.8z"
android:fillColor="#1A73E8"/>
<path
android:pathData="M142.7,67.9h-11.5c-1.6,0 -2.9,1.3 -2.9,2.9H67.8c-7.9,0 -14.4,6.5 -14.4,14.4v132.4c0,7.9 6.5,14.4 14.4,14.4h276.4c7.9,0 14.4,-6.5 14.4,-14.4V85.2c0,-7.9 -6.5,-14.4 -14.4,-14.4H203.1c0,-1.6 -1.3,-2.9 -2.9,-2.9h-28.8c-1.6,0 -2.9,1.3 -2.9,2.9h-23C145.5,69.2 144.3,67.9 142.7,67.9zM344.2,73.7c6.4,0 11.5,5.2 11.5,11.5v132.4c0,6.3 -5.2,11.5 -11.5,11.5H67.8c-6.4,0 -11.5,-5.2 -11.5,-11.5V85.2c0,-6.3 5.2,-11.5 11.5,-11.5H344.2z"
android:fillColor="#DADCE0"/>
</vector>

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.spa.widget
import androidx.annotation.DrawableRes
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.filterToOne
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.tests.R
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class IllustrationTest {
@get:Rule
val composeTestRule = createComposeRule()
private val DrawableId = SemanticsPropertyKey<Int>("DrawableResId")
private var SemanticsPropertyReceiver.drawableId by DrawableId
@Test
fun image_displayed() {
val resId = R.drawable.accessibility_captioning_banner
composeTestRule.setContent {
Illustration(
resId = resId,
resourceType = ResourceType.IMAGE,
modifier = Modifier.semantics { drawableId = resId }
)
}
fun hasDrawable(@DrawableRes id: Int): SemanticsMatcher =
SemanticsMatcher.expectValue(DrawableId, id)
val isIllustrationNode = !hasAnyAncestor(hasDrawable(resId))
composeTestRule.onAllNodes(hasDrawable(resId))
.filterToOne(isIllustrationNode)
.assertIsDisplayed()
}
}