diff --git a/packages/SettingsLib/Spa/gallery/res/drawable/accessibility_captioning_banner.xml b/packages/SettingsLib/Spa/gallery/res/drawable/accessibility_captioning_banner.xml new file mode 100644 index 0000000000000..6597ffb576884 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/res/drawable/accessibility_captioning_banner.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt index 43cfbb3f4f6d1..6d5101fef8914 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt @@ -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), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt index ad492a5bb161d..2428bba1dd4cb 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt @@ -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. diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt new file mode 100644 index 0000000000000..5db6e7ac1d36c --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt @@ -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() + } +} diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle index ad69da3147354..104966d9b097e 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle +++ b/packages/SettingsLib/Spa/spa/build.gradle @@ -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' } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index c1a3c20181868..c2223e684f291 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -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 } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt new file mode 100644 index 0000000000000..0d4e0c5059431 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt @@ -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, + ) + } + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Image.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Image.kt new file mode 100644 index 0000000000000..0790bf8bca6c0 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Image.kt @@ -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, + ) + } +} diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle index 5f93a9f4e4b5b..21d696f7e90c7 100644 --- a/packages/SettingsLib/Spa/tests/build.gradle +++ b/packages/SettingsLib/Spa/tests/build.gradle @@ -31,6 +31,9 @@ android { } sourceSets { + main { + res.srcDirs = ["res"] + } androidTest { kotlin { srcDir "src" diff --git a/packages/SettingsLib/Spa/tests/res/drawable/accessibility_captioning_banner.xml b/packages/SettingsLib/Spa/tests/res/drawable/accessibility_captioning_banner.xml new file mode 100644 index 0000000000000..6597ffb576884 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/res/drawable/accessibility_captioning_banner.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt new file mode 100644 index 0000000000000..d2a07e9e041ca --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt @@ -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("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() + } +}