Merge changes from topic "gallery-dark"

* changes:
  Force the app dark/light theme in the Gallery app (1/2)
  Represent the Gallery screen hierarchy as a tree
This commit is contained in:
Jordan Demeulenaere
2022-07-27 07:32:38 +00:00
committed by Android (Google) Code Review
11 changed files with 213 additions and 126 deletions

View File

@@ -37,7 +37,7 @@ val LocalAndroidColorScheme =
* Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
* most of the colors in this class will be removed in favor of their M3 counterpart.
*/
class AndroidColorScheme internal constructor(private val context: Context) {
class AndroidColorScheme internal constructor(context: Context) {
val colorPrimary = getColor(context, R.attr.colorPrimary)
val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
val colorAccent = getColor(context, R.attr.colorAccent)

View File

@@ -34,6 +34,7 @@ android_library {
],
static_libs: [
"SystemUI-core",
"SystemUIComposeCore",
"SystemUIComposeFeatures",

View File

@@ -16,7 +16,40 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.systemui.compose.gallery">
<!-- To emulate a display size and density. -->
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application
android:name="android.app.Application"
android:appComponentFactory="androidx.core.app.AppComponentFactory"
tools:replace="android:name,android:appComponentFactory">
<!-- Disable providers from SystemUI -->
<provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
android:authorities="com.android.systemui.test.keyguard.disabled"
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
<provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
android:authorities="com.android.systemui.test.keyguard.disabled"
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
<provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
android:authorities="com.android.systemui.test.keyguard.clock.disabled"
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
<provider android:name="com.android.systemui.people.PeopleProvider"
android:authorities="com.android.systemui.test.people.disabled"
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
<provider android:name="androidx.core.content.FileProvider"
android:authorities="com.android.systemui.test.fileprovider.disabled"
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove"/>
</application>
</manifest>

View File

@@ -16,6 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.systemui.compose.gallery.app">
<application
android:allowBackup="true"
@@ -23,7 +24,8 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SystemUIGallery">
android:theme="@style/Theme.SystemUI.Gallery"
tools:replace="android:icon,android:theme,android:label">
<activity
android:name="com.android.systemui.compose.gallery.GalleryActivity"
android:exported="true"

View File

@@ -1,26 +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 xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.SystemUIGallery" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:statusBarColor" tools:targetApi="l">
@android:color/transparent
</item>
<item name="android:navigationBarColor" tools:targetApi="l">
@android:color/transparent
</item>
</style>
</resources>

View File

@@ -15,7 +15,10 @@
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.SystemUIGallery" parent="Theme.AppCompat.DayNight.NoActionBar">
<style name="Theme.SystemUI.Gallery">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:statusBarColor" tools:targetApi="l">
@android:color/transparent
</item>
@@ -24,4 +27,4 @@
</item>
<item name="android:windowLightStatusBar">true</item>
</style>
</resources>
</resources>

View File

@@ -9,11 +9,12 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BrightnessHigh
import androidx.compose.material.icons.filled.BrightnessLow
import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.FormatSize
import androidx.compose.material.icons.filled.FormatTextdirectionLToR
import androidx.compose.material.icons.filled.FormatTextdirectionRToL
import androidx.compose.material.icons.filled.InvertColors
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.Smartphone
import androidx.compose.material.icons.filled.Tablet
import androidx.compose.material3.Button
@@ -44,12 +45,13 @@ enum class FontScale(val scale: Float) {
/** A configuration panel that allows to toggle the theme, font scale and layout direction. */
@Composable
fun ConfigurationControls(
isDarkTheme: Boolean,
theme: Theme,
fontScale: FontScale,
layoutDirection: LayoutDirection,
onChangeTheme: () -> Unit,
onChangeLayoutDirection: () -> Unit,
onChangeFontScale: () -> Unit,
modifier: Modifier = Modifier,
) {
// The display we are emulating, if any.
var emulatedDisplayName by rememberSaveable { mutableStateOf<String?>(null) }
@@ -84,18 +86,26 @@ fun ConfigurationControls(
// TODO(b/231131244): Fork FlowRow from Accompanist and use that instead to make sure that users
// don't miss any available configuration.
LazyRow {
LazyRow(modifier) {
// Dark/light theme.
item {
TextButton(onChangeTheme) {
val text: String
val icon: ImageVector
if (isDarkTheme) {
icon = Icons.Default.BrightnessHigh
text = "Dark"
} else {
icon = Icons.Default.BrightnessLow
text = "Light"
when (theme) {
Theme.System -> {
icon = Icons.Default.InvertColors
text = "System"
}
Theme.Dark -> {
icon = Icons.Default.DarkMode
text = "Dark"
}
Theme.Light -> {
icon = Icons.Default.LightMode
text = "Light"
}
}
Icon(icon, null)

View File

@@ -16,6 +16,8 @@
package com.android.systemui.compose.gallery
import android.app.UiModeManager
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -23,7 +25,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.core.view.WindowCompat
@@ -33,22 +35,46 @@ class GalleryActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
setContent {
val isSystemInDarkTheme = isSystemInDarkTheme()
var isDarkTheme by remember { mutableStateOf(isSystemInDarkTheme) }
val onChangeTheme = { isDarkTheme = !isDarkTheme }
var theme by rememberSaveable { mutableStateOf(Theme.System) }
val onChangeTheme = {
// Change to the next theme for a toggle behavior.
theme =
when (theme) {
Theme.System -> Theme.Dark
Theme.Dark -> Theme.Light
Theme.Light -> Theme.System
}
}
val isSystemInDarkTheme = isSystemInDarkTheme()
val isDark = theme == Theme.Dark || (theme == Theme.System && isSystemInDarkTheme)
val useDarkIcons = !isDark
val systemUiController = rememberSystemUiController()
val useDarkIcons = !isDarkTheme
SideEffect {
systemUiController.setSystemBarsColor(
color = Color.Transparent,
darkIcons = useDarkIcons,
)
uiModeManager.setApplicationNightMode(
when (theme) {
Theme.System -> UiModeManager.MODE_NIGHT_AUTO
Theme.Dark -> UiModeManager.MODE_NIGHT_YES
Theme.Light -> UiModeManager.MODE_NIGHT_NO
}
)
}
GalleryApp(isDarkTheme, onChangeTheme)
GalleryApp(theme, onChangeTheme)
}
}
}
enum class Theme {
System,
Dark,
Light,
}

View File

@@ -23,36 +23,38 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.android.systemui.compose.theme.SystemUITheme
enum class Screen {
Home,
Typography,
MaterialColors,
AndroidColors,
ExampleFeature,
/** The gallery app screens. */
object GalleryAppScreens {
val Typography = ChildScreen("typography") { TypographyScreen() }
val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() }
val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() }
val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() }
val Home =
ParentScreen(
"home",
mapOf(
"Typography" to Typography,
"Material colors" to MaterialColors,
"Android colors" to AndroidColors,
"Example feature" to ExampleFeature,
)
)
}
/** The main content of the app, that shows the [HomeScreen] by default. */
/** The main content of the app, that shows [GalleryAppScreens.Home] by default. */
@Composable
private fun MainContent() {
Box(Modifier.fillMaxSize()) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Screen.Home.name,
startDestination = GalleryAppScreens.Home.identifier,
) {
composable(Screen.Home.name) {
HomeScreen(
onScreenSelected = { navController.navigate(it.name) },
)
}
composable(Screen.Typography.name) { TypographyScreen() }
composable(Screen.MaterialColors.name) { MaterialColorsScreen() }
composable(Screen.AndroidColors.name) { AndroidColorsScreen() }
composable(Screen.ExampleFeature.name) { ExampleFeatureScreen() }
screen(GalleryAppScreens.Home, navController)
}
}
}
@@ -63,7 +65,7 @@ private fun MainContent() {
*/
@Composable
fun GalleryApp(
isDarkTheme: Boolean,
theme: Theme,
onChangeTheme: () -> Unit,
) {
val systemFontScale = LocalDensity.current.fontScale
@@ -98,14 +100,14 @@ fun GalleryApp(
LocalDensity provides density,
LocalLayoutDirection provides layoutDirection,
) {
SystemUITheme(isDarkTheme) {
SystemUITheme {
Surface(
Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
) {
Column(Modifier.fillMaxSize().systemBarsPadding().padding(16.dp)) {
ConfigurationControls(
isDarkTheme,
theme,
fontScale,
layoutDirection,
onChangeTheme,

View File

@@ -1,59 +0,0 @@
/*
* 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.systemui.compose.gallery
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
/** The home screen shown when starting the app. */
@Composable
fun HomeScreen(onScreenSelected: (Screen) -> Unit) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Screen.values()
.filter { it != Screen.Home }
.forEach { screen ->
item {
Surface(
Modifier.fillMaxWidth(),
color = MaterialTheme.colorScheme.secondaryContainer,
shape = CircleShape,
) {
Column(
Modifier.clickable { onScreenSelected(screen) }.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(screen.name)
}
}
}
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.systemui.compose.gallery
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
/**
* A screen in an app. It is either an [ParentScreen] which lists its child screens to navigate to
* them or a [ChildScreen] which shows some content.
*/
sealed class Screen(val identifier: String)
class ParentScreen(
identifier: String,
val children: Map<String, Screen>,
) : Screen(identifier)
class ChildScreen(
identifier: String,
val content: @Composable (NavController) -> Unit,
) : Screen(identifier)
/** Create the navigation graph for [screen]. */
fun NavGraphBuilder.screen(screen: Screen, navController: NavController) {
when (screen) {
is ChildScreen -> composable(screen.identifier) { screen.content(navController) }
is ParentScreen -> {
val menuRoute = "${screen.identifier}_menu"
navigation(startDestination = menuRoute, route = screen.identifier) {
// The menu to navigate to one of the children screens.
composable(menuRoute) { ScreenMenu(screen, navController) }
// The content of the child screens.
screen.children.forEach { (_, child) -> screen(child, navController) }
}
}
}
}
@Composable
private fun ScreenMenu(
screen: ParentScreen,
navController: NavController,
) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
screen.children.forEach { (name, child) ->
item {
Surface(
Modifier.fillMaxWidth(),
color = MaterialTheme.colorScheme.secondaryContainer,
shape = CircleShape,
) {
Column(
Modifier.clickable { navController.navigate(child.identifier) }
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(name)
}
}
}
}
}
}