Merge "Add implementation for BackupRestoreEventLogger"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 2820ed7..3b159e9 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -59,9 +59,9 @@
api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
- api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha02"
+ api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha03"
api "androidx.navigation:navigation-compose:2.5.0"
- api "com.google.android.material:material:1.6.1"
+ api "com.google.android.material:material:1.7.0-alpha03"
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/res/values-night/themes.xml b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
deleted file mode 100644
index 67dd2b0..0000000
--- a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<resources>
-
- <style name="Theme.SpaLib.DayNight" />
-</resources>
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index e0e5fc2..25846ec 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -16,12 +16,10 @@
-->
<resources>
- <style name="Theme.SpaLib" parent="Theme.Material3.DayNight.NoActionBar">
+ <style name="Theme.SpaLib" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
- </style>
-
- <style name="Theme.SpaLib.DayNight">
- <item name="android:windowLightStatusBar">true</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
</style>
</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index d3efaa7..c3c90ab 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -27,6 +27,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavGraph.Companion.findStartDestination
@@ -66,8 +67,9 @@
private val spaEnvironment get() = SpaEnvironmentFactory.instance
override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(R.style.Theme_SpaLib_DayNight)
+ setTheme(R.style.Theme_SpaLib)
super.onCreate(savedInstanceState)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
setContent {
@@ -83,35 +85,19 @@
val navController = rememberNavController()
val nullPage = SettingsPage.createNull()
CompositionLocalProvider(navController.localNavController()) {
- NavHost(navController, nullPage.sppName) {
+ NavHost(
+ navController = navController,
+ startDestination = nullPage.sppName,
+ ) {
composable(nullPage.sppName) {}
for (spp in sppRepository.getAllProviders()) {
composable(
route = spp.name + spp.parameter.navRoute(),
arguments = spp.parameter,
) { navBackStackEntry ->
- val lifecycleOwner = LocalLifecycleOwner.current
- val sp = remember(navBackStackEntry.arguments) {
+ PageLogger(remember(navBackStackEntry.arguments) {
spp.createSettingsPage(arguments = navBackStackEntry.arguments)
- }
-
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_START) {
- sp.enterPage()
- } else if (event == Lifecycle.Event.ON_STOP) {
- sp.leavePage()
- }
- }
-
- // Add the observer to the lifecycle
- lifecycleOwner.lifecycle.addObserver(observer)
-
- // When the effect leaves the Composition, remove the observer
- onDispose {
- lifecycleOwner.lifecycle.removeObserver(observer)
- }
- }
+ })
spp.Page(navBackStackEntry.arguments)
}
@@ -122,6 +108,28 @@
}
@Composable
+ private fun PageLogger(settingsPage: SettingsPage) {
+ val lifecycleOwner = LocalLifecycleOwner.current
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_START) {
+ settingsPage.enterPage()
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ settingsPage.leavePage()
+ }
+ }
+
+ // Add the observer to the lifecycle
+ lifecycleOwner.lifecycle.addObserver(observer)
+
+ // When the effect leaves the Composition, remove the observer
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+ }
+
+ @Composable
private fun InitialDestinationNavigator() {
val sppRepository by spaEnvironment.pageProviderRepository
val destinationNavigated = rememberSaveable { mutableStateOf(false) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt
new file mode 100644
index 0000000..18335ff
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.framework.compose
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+
+internal fun PaddingValues.horizontalValues(): PaddingValues = HorizontalPaddingValues(this)
+
+internal fun PaddingValues.verticalValues(): PaddingValues = VerticalPaddingValues(this)
+
+private class HorizontalPaddingValues(private val paddingValues: PaddingValues) : PaddingValues {
+ override fun calculateLeftPadding(layoutDirection: LayoutDirection) =
+ paddingValues.calculateLeftPadding(layoutDirection)
+
+ override fun calculateTopPadding(): Dp = 0.dp
+
+ override fun calculateRightPadding(layoutDirection: LayoutDirection) =
+ paddingValues.calculateRightPadding(layoutDirection)
+
+ override fun calculateBottomPadding() = 0.dp
+}
+
+private class VerticalPaddingValues(private val paddingValues: PaddingValues) : PaddingValues {
+ override fun calculateLeftPadding(layoutDirection: LayoutDirection) = 0.dp
+
+ override fun calculateTopPadding(): Dp = paddingValues.calculateTopPadding()
+
+ override fun calculateRightPadding(layoutDirection: LayoutDirection) = 0.dp
+
+ override fun calculateBottomPadding() = paddingValues.calculateBottomPadding()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
index 9eaa88a..26491d5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
@@ -62,7 +62,7 @@
private val spaEnvironment get() = SpaEnvironmentFactory.instance
override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(R.style.Theme_SpaLib_DayNight)
+ setTheme(R.style.Theme_SpaLib)
super.onCreate(savedInstanceState)
spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
index eb20ac5..711c8a7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
@@ -34,6 +35,7 @@
Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.background)
+ .systemBarsPadding()
.verticalScroll(rememberScrollState()),
) {
Text(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
index 9a17b2a..d17a8dc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
@@ -19,7 +19,7 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
@@ -27,6 +27,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is scrollable and wrapped in a [Column].
@@ -42,8 +44,9 @@
) {
SettingsScaffold(title, actions) { paddingValues ->
Column(Modifier.verticalScroll(rememberScrollState())) {
- Spacer(Modifier.padding(paddingValues))
+ Spacer(Modifier.height(paddingValues.calculateTopPadding()))
content()
+ Spacer(Modifier.height(paddingValues.calculateBottomPadding()))
}
}
}
@@ -52,6 +55,13 @@
@Composable
private fun RegularScaffoldPreview() {
SettingsTheme {
- RegularScaffold(title = "Display") {}
+ RegularScaffold(title = "Display") {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index 4f83ad6..efc623a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalMaterial3Api::class)
-
package com.android.settingslib.spa.widget.scaffold
import androidx.activity.compose.BackHandler
import androidx.appcompat.R
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -31,10 +30,13 @@
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
@@ -48,45 +50,57 @@
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settingslib.spa.framework.compose.hideKeyboardAction
+import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.theme.SettingsOpacity
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is can be full screen, and with a search feature built-in.
*/
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchScaffold(
title: String,
actions: @Composable RowScope.() -> Unit = {},
- content: @Composable (searchQuery: State<String>) -> Unit,
+ content: @Composable (bottomPadding: Dp, searchQuery: State<String>) -> Unit,
) {
val viewModel: SearchScaffoldViewModel = viewModel()
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
SearchableTopAppBar(
title = title,
actions = actions,
+ scrollBehavior = scrollBehavior,
searchQuery = viewModel.searchQuery,
) { viewModel.searchQuery = it }
},
) { paddingValues ->
Box(
Modifier
- .padding(paddingValues)
- .fillMaxSize()
+ .padding(paddingValues.horizontalValues())
+ .padding(top = paddingValues.calculateTopPadding())
+ .fillMaxSize(),
) {
- val searchQuery = remember {
- derivedStateOf { viewModel.searchQuery?.text ?: "" }
- }
- content(searchQuery)
+ content(
+ bottomPadding = paddingValues.calculateBottomPadding(),
+ searchQuery = remember {
+ derivedStateOf { viewModel.searchQuery?.text ?: "" }
+ },
+ )
}
}
}
@@ -95,10 +109,12 @@
var searchQuery: TextFieldValue? by mutableStateOf(null)
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchableTopAppBar(
title: String,
actions: @Composable RowScope.() -> Unit,
+ scrollBehavior: TopAppBarScrollBehavior,
searchQuery: TextFieldValue?,
onSearchQueryChange: (TextFieldValue?) -> Unit,
) {
@@ -110,13 +126,17 @@
actions = actions,
)
} else {
- SettingsTopAppBar(title) {
- SearchAction { onSearchQueryChange(TextFieldValue()) }
+ SettingsTopAppBar(title, scrollBehavior) {
+ SearchAction {
+ scrollBehavior.collapse()
+ onSearchQueryChange(TextFieldValue())
+ }
actions()
}
}
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchTopAppBar(
query: TextFieldValue,
@@ -124,21 +144,24 @@
onClose: () -> Unit,
actions: @Composable RowScope.() -> Unit = {},
) {
- TopAppBar(
- title = { SearchBox(query, onQueryChange) },
- modifier = Modifier.statusBarsPadding(),
- navigationIcon = { CollapseAction(onClose) },
- actions = {
- if (query.text.isNotEmpty()) {
- ClearAction { onQueryChange(TextFieldValue()) }
- }
- actions()
- },
- colors = settingsTopAppBarColors(),
- )
+ Surface(color = SettingsTheme.colorScheme.surfaceHeader) {
+ TopAppBar(
+ title = { SearchBox(query, onQueryChange) },
+ modifier = Modifier.statusBarsPadding(),
+ navigationIcon = { CollapseAction(onClose) },
+ actions = {
+ if (query.text.isNotEmpty()) {
+ ClearAction { onQueryChange(TextFieldValue()) }
+ }
+ actions()
+ },
+ colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Transparent),
+ )
+ }
BackHandler { onClose() }
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
val focusRequester = remember { FocusRequester() }
@@ -184,6 +207,15 @@
@Composable
private fun SearchScaffoldPreview() {
SettingsTheme {
- SearchScaffold(title = "App notifications") {}
+ SearchScaffold(title = "App notifications") { _, _ ->
+ Column {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index 3bc3dd7..f4e504a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -16,13 +16,23 @@
package com.android.settingslib.spa.widget.scaffold
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.compose.horizontalValues
+import com.android.settingslib.spa.framework.compose.verticalValues
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is can be full screen when needed.
@@ -34,16 +44,30 @@
actions: @Composable RowScope.() -> Unit = {},
content: @Composable (PaddingValues) -> Unit,
) {
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
- topBar = { SettingsTopAppBar(title, actions) },
- content = content,
- )
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ topBar = { SettingsTopAppBar(title, scrollBehavior, actions) },
+ ) { paddingValues ->
+ Box(Modifier.padding(paddingValues.horizontalValues())) {
+ content(paddingValues.verticalValues())
+ }
+ }
}
@Preview
@Composable
private fun SettingsScaffoldPreview() {
SettingsTheme {
- SettingsScaffold(title = "Display") {}
+ SettingsScaffold(title = "Display") { paddingValues ->
+ Column(Modifier.padding(paddingValues)) {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
index 9353520..f7cb035 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
@@ -17,41 +17,70 @@
package com.android.settingslib.spa.widget.scaffold
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LargeTopAppBar
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
+import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.rememberSettingsTypography
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun SettingsTopAppBar(
title: String,
+ scrollBehavior: TopAppBarScrollBehavior,
actions: @Composable RowScope.() -> Unit,
) {
- TopAppBar(
- title = {
- Text(
- text = title,
- modifier = Modifier.padding(SettingsDimension.itemPaddingAround),
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- )
- },
- navigationIcon = { NavigateBack() },
- actions = actions,
- colors = settingsTopAppBarColors(),
+ val colorScheme = MaterialTheme.colorScheme
+ // TODO: Remove MaterialTheme() after top app bar color fixed in AndroidX.
+ MaterialTheme(
+ colorScheme = remember { colorScheme.copy(surface = colorScheme.background) },
+ typography = rememberSettingsTypography(),
+ ) {
+ LargeTopAppBar(
+ title = { Title(title) },
+ navigationIcon = { NavigateBack() },
+ actions = actions,
+ colors = largeTopAppBarColors(),
+ scrollBehavior = scrollBehavior,
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+internal fun TopAppBarScrollBehavior.collapse() {
+ with(state) {
+ heightOffset = heightOffsetLimit
+ }
+}
+
+@Composable
+private fun Title(title: String) {
+ Text(
+ text = title,
+ modifier = Modifier
+ .padding(WindowInsets.navigationBars.asPaddingValues().horizontalValues())
+ .padding(SettingsDimension.itemPaddingAround),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-internal fun settingsTopAppBarColors() = TopAppBarDefaults.smallTopAppBarColors(
- containerColor = SettingsTheme.colorScheme.surfaceHeader,
+private fun largeTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.background,
scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt
new file mode 100644
index 0000000..1964c43
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.scaffold
+
+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 RegularScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun regularScaffold_titleIsDisplayed() {
+ composeTestRule.setContent {
+ RegularScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun regularScaffold_itemsAreDisplayed() {
+ composeTestRule.setContent {
+ RegularScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+ composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "title"
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
index ec3379d..c3e1d54 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
@@ -43,7 +43,7 @@
@Test
fun initialState_titleIsDisplayed() {
composeTestRule.setContent {
- SearchScaffold(title = TITLE) {}
+ SearchScaffold(title = TITLE) { _, _ -> }
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
@@ -116,7 +116,7 @@
private fun setContent(): State<String> {
lateinit var actualSearchQuery: State<String>
composeTestRule.setContent {
- SearchScaffold(title = TITLE) { searchQuery ->
+ SearchScaffold(title = TITLE) { _, searchQuery ->
SideEffect {
actualSearchQuery = searchQuery
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
similarity index 89%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
index 0c84eac..0c745d5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.spa.widget.scaffold
-import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsNotSelected
@@ -31,15 +30,13 @@
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-class SettingsPagerKtTest {
+class SettingsPagerTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun twoPage_initialState() {
- composeTestRule.setContent {
- TestTwoPage()
- }
+ setTwoPagesContent()
composeTestRule.onNodeWithText("Personal").assertIsSelected()
composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
@@ -49,9 +46,7 @@
@Test
fun twoPage_afterSwitch() {
- composeTestRule.setContent {
- TestTwoPage()
- }
+ setTwoPagesContent()
composeTestRule.onNodeWithText("Work").performClick()
@@ -73,11 +68,12 @@
composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
}
-}
-@Composable
-private fun TestTwoPage() {
- SettingsPager(listOf("Personal", "Work")) {
- SettingsTitle(title = "Page $it")
+ private fun setTwoPagesContent() {
+ composeTestRule.setContent {
+ SettingsPager(listOf("Personal", "Work")) {
+ SettingsTitle(title = "Page $it")
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
new file mode 100644
index 0000000..f042404
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.scaffold
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.Text
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun settingsScaffold_titleIsDisplayed() {
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsScaffold_itemsAreDisplayed() {
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+ composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsScaffold_noHorizontalPadding() {
+ lateinit var actualPaddingValues: PaddingValues
+
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) { paddingValues ->
+ SideEffect {
+ actualPaddingValues = paddingValues
+ }
+ }
+ }
+
+ assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
+ }
+
+ private companion object {
+ const val TITLE = "title"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 408b9df..3cd8378 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -25,12 +25,12 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.Dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settingslib.spa.framework.compose.LogCompositions
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppListConfig
@@ -55,10 +55,11 @@
option: State<Int>,
searchQuery: State<String>,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+ bottomPadding: Dp,
) {
LogCompositions(TAG, appListConfig.userId.toString())
val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
- AppListWidget(appListData, listModel, appItem)
+ AppListWidget(appListData, listModel, appItem, bottomPadding)
}
@Composable
@@ -66,6 +67,7 @@
appListData: State<AppListData<T>?>,
listModel: AppListModel<T>,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+ bottomPadding: Dp,
) {
val timeMeasurer = rememberTimeMeasurer(TAG)
appListData.value?.let { (list, option) ->
@@ -77,7 +79,7 @@
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
- contentPadding = PaddingValues(bottom = SettingsDimension.itemPaddingVertical),
+ contentPadding = PaddingValues(bottom = bottomPadding),
) {
items(count = list.size, key = { option to list[it].record.app.packageName }) {
val appEntry = list[it]
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 99376b0..2953367 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -52,7 +52,7 @@
actions = {
ShowSystemAction(showSystem.value) { showSystem.value = it }
},
- ) { searchQuery ->
+ ) { bottomPadding, searchQuery ->
WorkProfilePager(primaryUserOnly) { userInfo ->
Column(Modifier.fillMaxSize()) {
val options = remember { listModel.getSpinnerOptions() }
@@ -68,6 +68,7 @@
option = selectedOption,
searchQuery = searchQuery,
appItem = appItem,
+ bottomPadding = bottomPadding,
)
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index 03d9f2d..30d3820 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -357,5 +357,12 @@
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*/
public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION = "isDefaultSubscription";
+
+ /**
+ * The name of the active data subscription state column, see
+ * {@link SubscriptionManager#getActiveDataSubscriptionId()}.
+ */
+ public static final String COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION =
+ "isActiveDataSubscriptionId";
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
index 329bd9b..23566f7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
@@ -42,7 +42,7 @@
boolean isUsableSubscription, boolean isActiveSubscriptionId,
boolean isAvailableSubscription, boolean isDefaultVoiceSubscription,
boolean isDefaultSmsSubscription, boolean isDefaultDataSubscription,
- boolean isDefaultSubscription) {
+ boolean isDefaultSubscription, boolean isActiveDataSubscriptionId) {
this.subId = subId;
this.simSlotIndex = simSlotIndex;
this.carrierId = carrierId;
@@ -72,6 +72,7 @@
this.isDefaultSmsSubscription = isDefaultSmsSubscription;
this.isDefaultDataSubscription = isDefaultDataSubscription;
this.isDefaultSubscription = isDefaultSubscription;
+ this.isActiveDataSubscriptionId = isActiveDataSubscriptionId;
}
@PrimaryKey
@@ -165,6 +166,9 @@
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION)
public boolean isDefaultSubscription;
+ @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION)
+ public boolean isActiveDataSubscriptionId;
+
public int getSubId() {
return Integer.valueOf(subId);
}
@@ -213,6 +217,7 @@
result = 31 * result + Boolean.hashCode(isDefaultSmsSubscription);
result = 31 * result + Boolean.hashCode(isDefaultDataSubscription);
result = 31 * result + Boolean.hashCode(isDefaultSubscription);
+ result = 31 * result + Boolean.hashCode(isActiveDataSubscriptionId);
return result;
}
@@ -254,7 +259,8 @@
&& isDefaultVoiceSubscription == info.isDefaultVoiceSubscription
&& isDefaultSmsSubscription == info.isDefaultSmsSubscription
&& isDefaultDataSubscription == info.isDefaultDataSubscription
- && isDefaultSubscription == info.isDefaultSubscription;
+ && isDefaultSubscription == info.isDefaultSubscription
+ && isActiveDataSubscriptionId == info.isActiveDataSubscriptionId;
}
public String toString() {
@@ -317,6 +323,8 @@
.append(isDefaultDataSubscription)
.append(", isDefaultSubscription = ")
.append(isDefaultSubscription)
+ .append(", isActiveDataSubscriptionId = ")
+ .append(isActiveDataSubscriptionId)
.append(")}");
return builder.toString();
}