mirror of
https://github.com/BetterAndroid/FlexiUI.git
synced 2025-09-08 19:44:25 +08:00
feat: add Dialog
This commit is contained in:
@@ -23,4 +23,225 @@
|
|||||||
|
|
||||||
package com.highcapable.flexiui.window
|
package com.highcapable.flexiui.window
|
||||||
|
|
||||||
// TODO: To be implemented
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
|
||||||
|
import com.highcapable.betterandroid.compose.extension.ui.window.Dialog
|
||||||
|
import com.highcapable.betterandroid.compose.extension.ui.window.DialogPropertiesWrapper
|
||||||
|
import com.highcapable.flexiui.LocalColors
|
||||||
|
import com.highcapable.flexiui.LocalSizes
|
||||||
|
import com.highcapable.flexiui.LocalTypography
|
||||||
|
import com.highcapable.flexiui.component.AreaBox
|
||||||
|
import com.highcapable.flexiui.component.AreaBoxStyle
|
||||||
|
import com.highcapable.flexiui.component.LocalPrimaryButton
|
||||||
|
import com.highcapable.flexiui.component.LocalTextStyle
|
||||||
|
import com.highcapable.flexiui.utils.SubcomposeRow
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class FlexiDialogColors(
|
||||||
|
val titleTextColor: Color,
|
||||||
|
val contentTextColor: Color
|
||||||
|
)
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class FlexiDialogStyle(
|
||||||
|
val boxStyle: AreaBoxStyle,
|
||||||
|
val titleTextStyle: TextStyle,
|
||||||
|
val contentTextStyle: TextStyle,
|
||||||
|
val maxWidth: Dp,
|
||||||
|
val outPadding: ComponentPadding,
|
||||||
|
val titlePadding: ComponentPadding,
|
||||||
|
val contentPadding: ComponentPadding,
|
||||||
|
val buttonsSpacing: Dp
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FlexiDialog(
|
||||||
|
visible: Boolean,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
animated: Boolean = true,
|
||||||
|
colors: FlexiDialogColors = FlexiDialog.colors,
|
||||||
|
style: FlexiDialogStyle = FlexiDialog.style,
|
||||||
|
contentAlignment: Alignment = Alignment.TopStart,
|
||||||
|
properties: DialogPropertiesWrapper = DefaultDialogProperties,
|
||||||
|
title: (@Composable () -> Unit)? = null,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
confirmButton: @Composable (() -> Unit)? = null,
|
||||||
|
cancelButton: @Composable (() -> Unit)? = null,
|
||||||
|
neutralButton: @Composable (() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
@Composable
|
||||||
|
fun Content() {
|
||||||
|
title?.also { content ->
|
||||||
|
Box(modifier = Modifier.padding(style.titlePadding)) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalTextStyle provides style.titleTextStyle.copy(color = colors.titleTextColor),
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(modifier = Modifier.padding(style.contentPadding)) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalTextStyle provides style.contentTextStyle.copy(color = colors.contentTextColor),
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Buttons() {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(top = style.buttonsSpacing),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
neutralButton?.also { content ->
|
||||||
|
SubcomposeRow(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(bottom = style.buttonsSpacing),
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SubcomposeRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
count = if (cancelButton != null) 2 else 1,
|
||||||
|
spacingBetween = style.buttonsSpacing
|
||||||
|
) {
|
||||||
|
cancelButton?.invoke()
|
||||||
|
confirmButton?.also { content ->
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalPrimaryButton provides true,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BasicFlexiDialog(
|
||||||
|
visible = visible,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
modifier = Modifier.padding(style.outPadding).then(modifier),
|
||||||
|
animated = animated,
|
||||||
|
boxStyle = style.boxStyle,
|
||||||
|
maxWidth = style.maxWidth,
|
||||||
|
contentAlignment = contentAlignment,
|
||||||
|
properties = properties
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
val hasButtons = confirmButton != null || cancelButton != null || neutralButton != null
|
||||||
|
Content()
|
||||||
|
if (hasButtons) Buttons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BasicFlexiDialog(
|
||||||
|
visible: Boolean,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
animated: Boolean = true,
|
||||||
|
boxStyle: AreaBoxStyle = AreaBox.style,
|
||||||
|
maxWidth: Dp = DefaultMaxWidth,
|
||||||
|
contentAlignment: Alignment = Alignment.TopStart,
|
||||||
|
properties: DialogPropertiesWrapper = DefaultDialogProperties,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val animatedAlpha by animateFloatAsState(if (visible) 1f else 0f, tween(AnimationDuration))
|
||||||
|
val visibleScale = remember { Animatable(AnimationMinmumScale) }
|
||||||
|
if (visible) LaunchedEffect(Unit) {
|
||||||
|
visibleScale.animateTo(targetValue = 1.1f)
|
||||||
|
visibleScale.animateTo(targetValue = 1f)
|
||||||
|
}
|
||||||
|
val animatedScale by animateFloatAsState(
|
||||||
|
targetValue = if (visible) visibleScale.value else AnimationMinmumScale,
|
||||||
|
animationSpec = if (visible)
|
||||||
|
tween(easing = LinearOutSlowInEasing, durationMillis = AnimationDuration)
|
||||||
|
else tween(easing = FastOutSlowInEasing, durationMillis = AnimationDuration)
|
||||||
|
)
|
||||||
|
val animatedScrimColor by animateColorAsState(if (visible) properties.scrimColor else Color.Transparent)
|
||||||
|
val animation = animated && animatedAlpha > 0f
|
||||||
|
if (visible || animation) Dialog(
|
||||||
|
properties = properties.copy(scrimColor = animatedScrimColor),
|
||||||
|
onDismissRequest = onDismissRequest
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = if (animated)
|
||||||
|
Modifier.graphicsLayer {
|
||||||
|
scaleX = animatedScale
|
||||||
|
scaleY = animatedScale
|
||||||
|
alpha = animatedAlpha
|
||||||
|
}.then(modifier)
|
||||||
|
else Modifier.then(modifier)
|
||||||
|
) {
|
||||||
|
AreaBox(
|
||||||
|
modifier = Modifier.widthIn(max = maxWidth),
|
||||||
|
style = boxStyle,
|
||||||
|
contentAlignment = contentAlignment
|
||||||
|
) { content() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object FlexiDialog {
|
||||||
|
val colors: FlexiDialogColors
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = defaultFlexiDialogColors()
|
||||||
|
val style: FlexiDialogStyle
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = defaultFlexiDialogStyle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
private fun defaultFlexiDialogColors() = FlexiDialogColors(
|
||||||
|
titleTextColor = LocalColors.current.textPrimary,
|
||||||
|
contentTextColor = LocalColors.current.textSecondary
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
private fun defaultFlexiDialogStyle() = FlexiDialogStyle(
|
||||||
|
boxStyle = AreaBox.style.copy(padding = ComponentPadding(LocalSizes.current.spacingSecondary)),
|
||||||
|
titleTextStyle = LocalTypography.current.titleSecondary,
|
||||||
|
contentTextStyle = LocalTypography.current.primary,
|
||||||
|
maxWidth = DefaultMaxWidth,
|
||||||
|
outPadding = ComponentPadding(horizontal = DefaultHorizontalOutPadding),
|
||||||
|
titlePadding = ComponentPadding(LocalSizes.current.spacingSecondary),
|
||||||
|
contentPadding = ComponentPadding(LocalSizes.current.spacingSecondary),
|
||||||
|
buttonsSpacing = LocalSizes.current.spacingSecondary
|
||||||
|
)
|
||||||
|
|
||||||
|
private const val AnimationDuration = 250
|
||||||
|
private const val AnimationMinmumScale = 0.8f
|
||||||
|
|
||||||
|
private val DefaultMaxWidth = 300.dp
|
||||||
|
private val DefaultHorizontalOutPadding = 50.dp
|
||||||
|
|
||||||
|
private const val DefaultScrimOpacity = 0.35f
|
||||||
|
private val DefaultScrimColor = Color.Black.copy(alpha = DefaultScrimOpacity)
|
||||||
|
private val DefaultDialogProperties = DialogPropertiesWrapper(usePlatformDefaultWidth = false, scrimColor = DefaultScrimColor)
|
Reference in New Issue
Block a user