diff options
8 files changed, 328 insertions, 3 deletions
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index 60bf48c8b75e..8b136da04405 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.6.0-beta01" + extra["jetpackComposeVersion"] = "1.6.0-beta02" } subprojects { diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 90c7d46c3004..f4edb36b5e76 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -48,6 +48,7 @@ import com.android.settingslib.spa.gallery.preference.PreferencePageProvider import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider +import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider @@ -100,6 +101,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { SettingsExposedDropdownMenuCheckBoxProvider, SettingsTextFieldPasswordPageProvider, SearchScaffoldPageProvider, + SuwScaffoldPageProvider, CardPageProvider, CopyablePageProvider, ), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt index 1d897f77011e..6a2e5985a735 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt @@ -43,6 +43,7 @@ import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider +import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider @@ -59,6 +60,7 @@ object HomePageProvider : SettingsPageProvider { OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(), SearchScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt new file mode 100644 index 000000000000..6fc8de3ac49c --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 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.scaffold + +import android.os.Bundle +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.SignalCellularAlt +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.gallery.R +import com.android.settingslib.spa.widget.illustration.Illustration +import com.android.settingslib.spa.widget.illustration.IllustrationModel +import com.android.settingslib.spa.widget.illustration.ResourceType +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton +import com.android.settingslib.spa.widget.scaffold.SuwScaffold +import com.android.settingslib.spa.widget.ui.SettingsBody + +private const val TITLE = "Sample SuwScaffold" + +object SuwScaffoldPageProvider : SettingsPageProvider { + override val name = "SuwScaffold" + + private val owner = createSettingsPage() + + fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + + @Composable + override fun Page(arguments: Bundle?) { + Page() + } +} + +@Composable +private fun Page() { + SuwScaffold( + imageVector = Icons.Outlined.SignalCellularAlt, + title = "Connect to mobile network", + actionButton = BottomAppBarButton("Next") {}, + dismissButton = BottomAppBarButton("Cancel") {}, + ) { + Column(Modifier.padding(SettingsDimension.itemPadding)) { + SettingsBody("To add another SIM, download a new eSIM.") + } + Illustration(object : IllustrationModel { + override val resId = R.drawable.accessibility_captioning_banner + override val resourceType = ResourceType.IMAGE + }) + Column(Modifier.padding(SettingsDimension.itemPadding)) { + SettingsBody("To add another SIM, download a new eSIM.") + } + Illustration(object : IllustrationModel { + override val resId = R.drawable.accessibility_captioning_banner + override val resourceType = ResourceType.IMAGE + }) + } +} diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index acd90f3c4f4d..7eccfe5ed508 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { api("androidx.slice:slice-builders:1.1.0-alpha02") api("androidx.slice:slice-core:1.1.0-alpha02") api("androidx.slice:slice-view:1.1.0-alpha02") - api("androidx.compose.material3:material3:1.2.0-alpha11") + api("androidx.compose.material3:material3:1.2.0-alpha12") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") 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 5a1120e6c7a1..c143390f269c 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 @@ -37,11 +37,13 @@ object SettingsDimension { val itemPaddingAround = 8.dp val itemDividerHeight = 32.dp + val iconLarge = 48.dp + /** The size when app icon is displayed in list. */ val appIconItemSize = 32.dp /** The size when app icon is displayed in App info page. */ - val appIconInfoSize = 48.dp + val appIconInfoSize = iconLarge /** The vertical padding for buttons. */ val buttonPaddingVertical = 12.dp diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt new file mode 100644 index 000000000000..354b95ddcbfe --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 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.scaffold + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.toMediumWeight + +data class BottomAppBarButton( + val text: String, + val onClick: () -> Unit, +) + +@Composable +fun SuwScaffold( + imageVector: ImageVector, + title: String, + actionButton: BottomAppBarButton? = null, + dismissButton: BottomAppBarButton? = null, + content: @Composable ColumnScope.() -> Unit, +) { + ActivityTitle(title) + Scaffold { innerPadding -> + BoxWithConstraints( + Modifier + .padding(innerPadding) + .padding(top = SettingsDimension.itemPaddingAround) + ) { + // Use single column layout in portrait, two columns in landscape. + val useSingleColumn = maxWidth < maxHeight + if (useSingleColumn) { + Column { + Column( + Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + ) { + Header(imageVector, title) + content() + } + BottomBar(actionButton, dismissButton) + } + } else { + Column(Modifier.padding(horizontal = SettingsDimension.itemPaddingAround)) { + Row((Modifier.weight(1f))) { + Box(Modifier.weight(1f)) { + Header(imageVector, title) + } + Column( + Modifier + .weight(1f) + .verticalScroll(rememberScrollState())) { + content() + } + } + BottomBar(actionButton, dismissButton) + } + } + } + } +} + +@Composable +private fun Header( + imageVector: ImageVector, + title: String +) { + Column(Modifier.padding(SettingsDimension.itemPadding)) { + Icon( + imageVector = imageVector, + contentDescription = null, + modifier = Modifier + .padding(vertical = SettingsDimension.itemPaddingAround) + .size(SettingsDimension.iconLarge), + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = title, + modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingVertical), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.displaySmall, + ) + } +} + +@Composable +private fun BottomBar( + actionButton: BottomAppBarButton?, + dismissButton: BottomAppBarButton?, +) { + Row(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) { + dismissButton?.apply { + TextButton(onClick) { + ActionText(text) + } + } + Spacer(modifier = Modifier.weight(1f)) + actionButton?.apply { + Button(onClick) { + ActionText(text) + } + } + } +} + +@Composable +private fun ActionText(text: String) { + Text( + text = text, + style = MaterialTheme.typography.bodyMedium.toMediumWeight(), + ) +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt new file mode 100644 index 000000000000..35c9f78ada68 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2023 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.scaffold + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.SignalCellularAlt +import androidx.compose.material3.Text +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SuwScaffoldTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun suwScaffold_titleIsDisplayed() { + composeTestRule.setContent { + SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) { + Text(text = "AAA") + Text(text = "BBB") + } + } + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed() + } + + @Test + fun suwScaffold_itemsAreDisplayed() { + composeTestRule.setContent { + SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) { + Text(text = "AAA") + Text(text = "BBB") + } + } + + composeTestRule.onNodeWithText("AAA").assertIsDisplayed() + composeTestRule.onNodeWithText("BBB").assertIsDisplayed() + } + + @Test + fun suwScaffold_actionButtonDisplayed() { + composeTestRule.setContent { + SuwScaffold( + imageVector = Icons.Outlined.SignalCellularAlt, + title = TITLE, + actionButton = BottomAppBarButton(TEXT) {}, + ) {} + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + @Test + fun suwScaffold_dismissButtonDisplayed() { + composeTestRule.setContent { + SuwScaffold( + imageVector = Icons.Outlined.SignalCellularAlt, + title = TITLE, + dismissButton = BottomAppBarButton(TEXT) {}, + ) {} + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + private companion object { + const val TITLE = "Title" + const val TEXT = "Text" + } +} |