| /* |
| * Copyright 2021 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 androidx.compose.material3 |
| |
| 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.padding |
| import androidx.compose.material3.tokens.DialogTokens |
| 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.graphics.Shape |
| import androidx.compose.ui.layout.Layout |
| import androidx.compose.ui.layout.Placeable |
| import androidx.compose.ui.unit.Dp |
| import androidx.compose.ui.unit.dp |
| import kotlin.math.max |
| |
| @Composable |
| internal fun AlertDialogContent( |
| buttons: @Composable () -> Unit, |
| modifier: Modifier = Modifier, |
| icon: (@Composable () -> Unit)?, |
| title: (@Composable () -> Unit)?, |
| text: @Composable (() -> Unit)?, |
| shape: Shape, |
| containerColor: Color, |
| tonalElevation: Dp, |
| buttonContentColor: Color, |
| iconContentColor: Color, |
| titleContentColor: Color, |
| textContentColor: Color, |
| ) { |
| Surface( |
| modifier = modifier, |
| shape = shape, |
| color = containerColor, |
| tonalElevation = tonalElevation, |
| ) { |
| Column( |
| modifier = Modifier.padding(DialogPadding) |
| ) { |
| icon?.let { |
| CompositionLocalProvider(LocalContentColor provides iconContentColor) { |
| Box( |
| Modifier |
| .padding(IconPadding) |
| .align(Alignment.CenterHorizontally) |
| ) { |
| icon() |
| } |
| } |
| } |
| title?.let { |
| CompositionLocalProvider(LocalContentColor provides titleContentColor) { |
| val textStyle = MaterialTheme.typography.fromToken(DialogTokens.HeadlineFont) |
| ProvideTextStyle(textStyle) { |
| 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 { |
| CompositionLocalProvider(LocalContentColor provides textContentColor) { |
| val textStyle = |
| MaterialTheme.typography.fromToken(DialogTokens.SupportingTextFont) |
| ProvideTextStyle(textStyle) { |
| Box( |
| Modifier |
| .weight(weight = 1f, fill = false) |
| .padding(TextPadding) |
| .align(Alignment.Start) |
| ) { |
| text() |
| } |
| } |
| } |
| } |
| Box(modifier = Modifier.align(Alignment.End)) { |
| CompositionLocalProvider(LocalContentColor provides buttonContentColor) { |
| val textStyle = |
| MaterialTheme.typography.fromToken(DialogTokens.ActionLabelTextFont) |
| ProvideTextStyle(value = textStyle, content = buttons) |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Simple clone of FlowRow that arranges its children in a horizontal flow with limited |
| * customization. |
| */ |
| @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. |
| sequences.add(0, currentSequence.toList()) |
| crossAxisSizes += currentCrossAxisSize |
| crossAxisPositions += crossAxisSpace |
| |
| crossAxisSpace += currentCrossAxisSize |
| mainAxisSpace = max(mainAxisSpace, currentMainAxisSize) |
| |
| currentSequence.clear() |
| currentMainAxisSize = 0 |
| currentCrossAxisSize = 0 |
| } |
| |
| for (measurable in measurables) { |
| // 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.forEachIndexed { 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.forEachIndexed { j, placeable -> |
| placeable.place( |
| x = mainAxisPositions[j], |
| y = crossAxisPositions[i] |
| ) |
| } |
| } |
| } |
| } |
| } |
| |
| internal val DialogMinWidth = 280.dp |
| internal val DialogMaxWidth = 560.dp |
| |
| // 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) |