diff options
| author | 2024-04-03 15:22:39 +0800 | |
|---|---|---|
| committer | 2024-04-29 11:39:27 +0000 | |
| commit | 3b7dcd8d2384ee2edec3fdcf45919b248f8106b0 (patch) | |
| tree | 8fa71377c7ca8fa23afacd05f470144a46d2fa08 | |
| parent | cc4ab8ae82749ddd9f80fdd20d46ba800d1d179d (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.xml | 5 | ||||
| -rw-r--r-- | packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt | 309 |
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 + ) +} |