blob: 990d060207df1d7f7d575140e3b2e212977bd626 [file] [log] [blame]
package com.android.systemui.compose.gallery
import android.graphics.Point
import android.os.UserHandle
import android.view.Display
import android.view.WindowManagerGlobal
import androidx.compose.foundation.layout.RowScope
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.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
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import kotlin.math.max
import kotlin.math.min
enum class FontScale(val scale: Float) {
Small(0.85f),
Normal(1f),
Big(1.15f),
Bigger(1.30f),
}
/** A configuration panel that allows to toggle the theme, font scale and layout direction. */
@Composable
fun ConfigurationControls(
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) }
val emulatedDisplay =
emulatedDisplayName?.let { name -> EmulatedDisplays.firstOrNull { it.name == name } }
LaunchedEffect(emulatedDisplay) {
val wm = WindowManagerGlobal.getWindowManagerService()
val defaultDisplayId = Display.DEFAULT_DISPLAY
if (emulatedDisplay == null) {
wm.clearForcedDisplayDensityForUser(defaultDisplayId, UserHandle.myUserId())
wm.clearForcedDisplaySize(defaultDisplayId)
} else {
val density = emulatedDisplay.densityDpi
// Emulate the display and make sure that we use the maximum available space possible.
val initialSize = Point()
wm.getInitialDisplaySize(defaultDisplayId, initialSize)
val width = emulatedDisplay.width
val height = emulatedDisplay.height
val minOfSize = min(width, height)
val maxOfSize = max(width, height)
if (initialSize.x < initialSize.y) {
wm.setForcedDisplaySize(defaultDisplayId, minOfSize, maxOfSize)
} else {
wm.setForcedDisplaySize(defaultDisplayId, maxOfSize, minOfSize)
}
wm.setForcedDisplayDensityForUser(defaultDisplayId, density, UserHandle.myUserId())
}
}
// TODO(b/231131244): Fork FlowRow from Accompanist and use that instead to make sure that users
// don't miss any available configuration.
LazyRow(modifier) {
// Dark/light theme.
item {
TextButton(onChangeTheme) {
val text: String
val icon: ImageVector
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)
Spacer(Modifier.width(8.dp))
Text(text)
}
}
// Font scale.
item {
TextButton(onChangeFontScale) {
Icon(Icons.Default.FormatSize, null)
Spacer(Modifier.width(8.dp))
Text(fontScale.name)
}
}
// Layout direction.
item {
TextButton(onChangeLayoutDirection) {
when (layoutDirection) {
LayoutDirection.Ltr -> {
Icon(Icons.Default.FormatTextdirectionLToR, null)
Spacer(Modifier.width(8.dp))
Text("LTR")
}
LayoutDirection.Rtl -> {
Icon(Icons.Default.FormatTextdirectionRToL, null)
Spacer(Modifier.width(8.dp))
Text("RTL")
}
}
}
}
// Display emulation.
EmulatedDisplays.forEach { display ->
item {
DisplayButton(
display,
emulatedDisplay == display,
{ emulatedDisplayName = it?.name },
)
}
}
}
}
@Composable
private fun DisplayButton(
display: EmulatedDisplay,
selected: Boolean,
onChangeEmulatedDisplay: (EmulatedDisplay?) -> Unit,
) {
val onClick = {
if (selected) {
onChangeEmulatedDisplay(null)
} else {
onChangeEmulatedDisplay(display)
}
}
val content: @Composable RowScope.() -> Unit = {
Icon(display.icon, null)
Spacer(Modifier.width(8.dp))
Text(display.name)
}
if (selected) {
Button(onClick, contentPadding = ButtonDefaults.TextButtonContentPadding, content = content)
} else {
TextButton(onClick, content = content)
}
}
/** The displays that can be emulated from this Gallery app. */
private val EmulatedDisplays =
listOf(
EmulatedDisplay(
"Phone",
Icons.Default.Smartphone,
width = 1440,
height = 3120,
densityDpi = 560,
),
EmulatedDisplay(
"Tablet",
Icons.Default.Tablet,
width = 2560,
height = 1600,
densityDpi = 320,
),
)
private data class EmulatedDisplay(
val name: String,
val icon: ImageVector,
val width: Int,
val height: Int,
val densityDpi: Int,
)