summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chaohui Wang <chaohuiw@google.com> 2024-04-03 15:22:39 +0800
committer Tom Hsu <tomhsu@google.com> 2024-04-29 11:39:27 +0000
commit3b7dcd8d2384ee2edec3fdcf45919b248f8106b0 (patch)
tree8fa71377c7ca8fa23afacd05f470144a46d2fa08
parentcc4ab8ae82749ddd9f80fdd20d46ba800d1d179d (diff)
[Spa] Support override dialog window type
Cannot override window type for Compose dialog, so fallback to androidx.appcompat.app.AlertDialog. Bug: 332643450 fix: 337796129 Test: manual - with Settings Change-Id: I3328816b0ea31896fb5f0d1393afdace2b2497fe (cherry picked from commit 66c1c33c0fa71b25b7243f2543e8b25284ecf88d)
-rw-r--r--packages/SettingsLib/Spa/spa/res/values/themes.xml5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt309
2 files changed, 313 insertions, 1 deletions
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index b55dd1bc2439..a6d8ca4660cd 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -23,7 +23,10 @@
<item name="android:windowNoTitle">true</item>
</style>
- <style name="Theme.SpaLib.Dialog" parent="Theme.Material3.DayNight.Dialog"/>
+ <style name="Theme.SpaLib.Dialog" parent="Theme.Material3.DayNight.Dialog">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ </style>
+
<style name="Theme.SpaLib.BottomSheetDialog" parent="Theme.SpaLib">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt
new file mode 100644
index 000000000000..bef0bca1c5a4
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2024 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.dialog
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.WarningAmber
+import androidx.compose.material3.AlertDialogDefaults
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import kotlin.math.max
+
+@Composable
+fun SettingsAlertDialogContent(
+ confirmButton: AlertDialogButton?,
+ dismissButton: AlertDialogButton?,
+ title: String?,
+ icon: @Composable (() -> Unit)? = {
+ Icon(
+ Icons.Default.WarningAmber,
+ contentDescription = null
+ )
+ },
+ text: @Composable (() -> Unit)?,
+) {
+ SettingsAlertDialogContent(
+ buttons = {
+ AlertDialogFlowRow(
+ mainAxisSpacing = ButtonsMainAxisSpacing,
+ crossAxisSpacing = ButtonsCrossAxisSpacing
+ ) {
+ dismissButton?.let {
+ OutlinedButton(onClick = it.onClick) {
+ Text(it.text)
+ }
+ }
+ confirmButton?.let {
+ Button(
+ onClick = {
+ it.onClick()
+ },
+ ) {
+ Text(it.text)
+ }
+ }
+ }
+ },
+ icon = icon,
+ modifier = Modifier.width(getDialogWidth()),
+ title = title?.let {
+ {
+ Text(
+ it,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ }
+ },
+ text = text?.let {
+ {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ text()
+ }
+ }
+ },
+ )
+}
+
+@Composable
+internal fun SettingsAlertDialogContent(
+ buttons: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ icon: (@Composable () -> Unit)?,
+ title: (@Composable () -> Unit)?,
+ text: @Composable (() -> Unit)?,
+) {
+ Surface(
+ modifier = modifier,
+ shape = SettingsShape.CornerExtraLarge,
+ color = MaterialTheme.colorScheme.surfaceContainerHigh,
+ ) {
+ Column(
+ modifier = Modifier.padding(DialogPadding)
+ ) {
+ icon?.let {
+ CompositionLocalProvider(
+ LocalContentColor provides AlertDialogDefaults.iconContentColor,
+ ) {
+ Box(
+ Modifier
+ .padding(IconPadding)
+ .align(Alignment.CenterHorizontally)
+ ) {
+ icon()
+ }
+ }
+ }
+ title?.let {
+ ProvideContentColorTextStyle(
+ contentColor = AlertDialogDefaults.titleContentColor,
+ textStyle = MaterialTheme.typography.headlineSmall,
+ ) {
+ Box(
+ // Align the title to the center when an icon is present.
+ Modifier
+ .padding(TitlePadding)
+ .align(
+ if (icon == null) {
+ Alignment.Start
+ } else {
+ Alignment.CenterHorizontally
+ }
+ )
+ ) {
+ title()
+ }
+ }
+ }
+ text?.let {
+ ProvideContentColorTextStyle(
+ contentColor = AlertDialogDefaults.textContentColor,
+ textStyle = MaterialTheme.typography.bodyMedium
+ ) {
+ Box(
+ Modifier
+ .weight(weight = 1f, fill = false)
+ .padding(TextPadding)
+ .align(Alignment.Start)
+ ) {
+ text()
+ }
+ }
+ }
+ Box(modifier = Modifier.align(Alignment.End)) {
+ ProvideContentColorTextStyle(
+ contentColor = MaterialTheme.colorScheme.primary,
+ textStyle = MaterialTheme.typography.labelLarge,
+ content = buttons
+ )
+ }
+ }
+ }
+}
+
+@Composable
+internal fun AlertDialogFlowRow(
+ mainAxisSpacing: Dp,
+ crossAxisSpacing: Dp,
+ content: @Composable () -> Unit
+) {
+ Layout(content) { measurables, constraints ->
+ val sequences = mutableListOf<List<Placeable>>()
+ val crossAxisSizes = mutableListOf<Int>()
+ val crossAxisPositions = mutableListOf<Int>()
+
+ var mainAxisSpace = 0
+ var crossAxisSpace = 0
+
+ val currentSequence = mutableListOf<Placeable>()
+ var currentMainAxisSize = 0
+ var currentCrossAxisSize = 0
+
+ // Return whether the placeable can be added to the current sequence.
+ fun canAddToCurrentSequence(placeable: Placeable) =
+ currentSequence.isEmpty() || currentMainAxisSize + mainAxisSpacing.roundToPx() +
+ placeable.width <= constraints.maxWidth
+
+ // Store current sequence information and start a new sequence.
+ fun startNewSequence() {
+ if (sequences.isNotEmpty()) {
+ crossAxisSpace += crossAxisSpacing.roundToPx()
+ }
+ // Ensures that confirming actions appear above dismissive actions.
+ @Suppress("ListIterator")
+ sequences.add(0, currentSequence.toList())
+ crossAxisSizes += currentCrossAxisSize
+ crossAxisPositions += crossAxisSpace
+
+ crossAxisSpace += currentCrossAxisSize
+ mainAxisSpace = max(mainAxisSpace, currentMainAxisSize)
+
+ currentSequence.clear()
+ currentMainAxisSize = 0
+ currentCrossAxisSize = 0
+ }
+
+ measurables.fastForEach { measurable ->
+ // Ask the child for its preferred size.
+ val placeable = measurable.measure(constraints)
+
+ // Start a new sequence if there is not enough space.
+ if (!canAddToCurrentSequence(placeable)) startNewSequence()
+
+ // Add the child to the current sequence.
+ if (currentSequence.isNotEmpty()) {
+ currentMainAxisSize += mainAxisSpacing.roundToPx()
+ }
+ currentSequence.add(placeable)
+ currentMainAxisSize += placeable.width
+ currentCrossAxisSize = max(currentCrossAxisSize, placeable.height)
+ }
+
+ if (currentSequence.isNotEmpty()) startNewSequence()
+
+ val mainAxisLayoutSize = max(mainAxisSpace, constraints.minWidth)
+
+ val crossAxisLayoutSize = max(crossAxisSpace, constraints.minHeight)
+
+ val layoutWidth = mainAxisLayoutSize
+
+ val layoutHeight = crossAxisLayoutSize
+
+ layout(layoutWidth, layoutHeight) {
+ sequences.fastForEachIndexed { i, placeables ->
+ val childrenMainAxisSizes = IntArray(placeables.size) { j ->
+ placeables[j].width +
+ if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0
+ }
+ val arrangement = Arrangement.End
+ val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 }
+ with(arrangement) {
+ arrange(
+ mainAxisLayoutSize, childrenMainAxisSizes,
+ layoutDirection, mainAxisPositions
+ )
+ }
+ placeables.fastForEachIndexed { j, placeable ->
+ placeable.place(
+ x = mainAxisPositions[j],
+ y = crossAxisPositions[i]
+ )
+ }
+ }
+ }
+ }
+}
+
+// Paddings for each of the dialog's parts.
+private val DialogPadding = PaddingValues(all = 24.dp)
+private val IconPadding = PaddingValues(bottom = 16.dp)
+private val TitlePadding = PaddingValues(bottom = 16.dp)
+private val TextPadding = PaddingValues(bottom = 24.dp)
+
+private val ButtonsMainAxisSpacing = 8.dp
+private val ButtonsCrossAxisSpacing = 12.dp
+
+/**
+ * ProvideContentColorTextStyle
+ *
+ * A convenience method to provide values to both LocalContentColor and LocalTextStyle in
+ * one call. This is less expensive than nesting calls to CompositionLocalProvider.
+ *
+ * Text styles will be merged with the current value of LocalTextStyle.
+ */
+@Composable
+private fun ProvideContentColorTextStyle(
+ contentColor: Color,
+ textStyle: TextStyle,
+ content: @Composable () -> Unit
+) {
+ val mergedStyle = LocalTextStyle.current.merge(textStyle)
+ CompositionLocalProvider(
+ LocalContentColor provides contentColor,
+ LocalTextStyle provides mergedStyle,
+ content = content
+ )
+}