feat: add ActionBar

This commit is contained in:
2023-12-02 14:56:31 +08:00
parent d6915d9135
commit 04c6fbfd9d

View File

@@ -23,4 +23,295 @@
package com.highcapable.flexiui.component
// TODO: To be implemented
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.highcapable.flexiui.LocalColors
import com.highcapable.flexiui.LocalSizes
import com.highcapable.flexiui.LocalTypography
import com.highcapable.flexiui.resources.Icons
import com.highcapable.flexiui.resources.icon.ArrowNaviUp
import com.highcapable.flexiui.resources.icon.FinishClose
@Immutable
data class ActionBarColors(
val titleTextColor: Color,
val subTextColor: Color,
val actionContentColor: Color
)
@Immutable
data class ActionBarStyle(
val padding: PaddingValues,
val contentSpacing: Dp,
val titleTextStyle: TextStyle,
val subTextStyle: TextStyle,
val actionIconPadding: Dp,
val actionIconMaxSize: Dp,
val actionContentMaxWidth: Dp
)
@Composable
fun TopActionBar(
modifier: Modifier = Modifier,
colors: ActionBarColors? = null,
style: ActionBarStyle? = null,
titleText: @Composable () -> Unit,
subText: @Composable (() -> Unit)? = null,
actions: @Composable (BasicActionBar.() -> Unit)? = null
) {
BasicActionBar(
type = ActionBarType.LARGE,
modifier = modifier,
colors = colors,
style = style,
titleText = titleText,
subText = subText,
finishIcon = null,
navigationIcon = null,
actions = actions
)
}
@Composable
fun ActionBar(
modifier: Modifier = Modifier,
colors: ActionBarColors? = null,
style: ActionBarStyle? = null,
titleText: @Composable () -> Unit,
subText: @Composable (() -> Unit)? = null,
finishIcon: @Composable (BasicActionBar.() -> Unit)? = null,
navigationIcon: @Composable BasicActionBar.() -> Unit,
actions: @Composable (BasicActionBar.() -> Unit)? = null
) {
BasicActionBar(
type = ActionBarType.MIDDLE,
modifier = modifier,
colors = colors,
style = style,
finishIcon = finishIcon,
navigationIcon = navigationIcon,
titleText = titleText,
subText = subText,
actions = actions
)
}
@Composable
private fun BasicActionBar(
type: ActionBarType,
modifier: Modifier,
colors: ActionBarColors?,
style: ActionBarStyle?,
titleText: @Composable () -> Unit,
subText: @Composable (() -> Unit)?,
finishIcon: @Composable (BasicActionBar.() -> Unit)?,
navigationIcon: @Composable (BasicActionBar.() -> Unit)?,
actions: @Composable (BasicActionBar.() -> Unit)?
) {
CompositionLocalProvider(LocalActionBarType provides type) {
val currentColors = colors ?: ActionBar.colors
val currentStyle = style ?: ActionBar.style
Box(modifier = modifier.padding(currentStyle.padding)) {
BasicActionBar(
type = type,
colors = currentColors,
style = currentStyle,
titleText = titleText,
subText = subText,
finishIcon = finishIcon,
navigationIcon = navigationIcon,
actions = actions
).Content()
}
}
}
@Immutable
class BasicActionBar internal constructor(
private val type: ActionBarType,
private val colors: ActionBarColors,
private val style: ActionBarStyle,
private val titleText: @Composable () -> Unit,
private val subText: @Composable (() -> Unit)?,
private val finishIcon: @Composable (BasicActionBar.() -> Unit)?,
private val navigationIcon: @Composable (BasicActionBar.() -> Unit)?,
private val actions: @Composable (BasicActionBar.() -> Unit)?
) {
@Composable
fun FinishIcon(onClick: () -> Unit) {
ActionIcon(onClick = onClick) { Icon(imageVector = Icons.FinishClose) }
}
@Composable
fun NavigationIcon(onClick: () -> Unit) {
ActionIcon(onClick = onClick) { Icon(imageVector = Icons.ArrowNaviUp) }
}
@Composable
fun ActionIcon(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
val iconInflateSize = style.actionIconMaxSize + style.actionIconPadding
IconButton(
onClick = onClick,
modifier = Modifier.size(iconInflateSize).then(modifier)
) { Box(modifier = Modifier.size(style.actionIconMaxSize)) { content() } }
}
@Composable
internal fun Content() {
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
val contentMaxWidth = maxWidth
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
val actionContentMaxWidth = if (actions != null) style.actionContentMaxWidth else 0.dp
Row(
modifier = Modifier.padding(end = style.contentSpacing)
.widthIn(max = contentMaxWidth - actionContentMaxWidth),
horizontalArrangement = Arrangement.spacedBy(style.contentSpacing),
verticalAlignment = Alignment.CenterVertically
) {
StartContent()
CenterContent()
}
EndContent()
}
}
}
@Composable
private fun StartContent() {
if (type == ActionBarType.MIDDLE)
Row(
horizontalArrangement = Arrangement.spacedBy(style.contentSpacing),
verticalAlignment = Alignment.CenterVertically
) {
ContentStyle(colors.actionContentColor) {
finishIcon?.also { it() }
navigationIcon?.also { it() }
}
}
}
@Composable
private fun CenterContent() {
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(style.contentSpacing / 2)
) {
ContentStyle(
color = colors.titleTextColor,
textStyle = style.titleTextStyle,
content = titleText
)
subText?.also { content ->
ContentStyle(
color = colors.subTextColor,
textStyle = style.subTextStyle,
content = content
)
}
}
}
@Composable
private fun EndContent() {
actions?.also { content ->
val scrollState = rememberScrollState()
Row(
modifier = Modifier.horizontalScroll(scrollState),
horizontalArrangement = Arrangement.spacedBy(style.contentSpacing),
verticalAlignment = Alignment.CenterVertically
) { ContentStyle(colors.actionContentColor) { content() } }
}
}
@Composable
private fun ContentStyle(
color: Color,
textStyle: TextStyle? = null,
content: @Composable () -> Unit
) {
CompositionLocalProvider(
LocalIconTint provides color,
LocalTextStyle provides LocalTextStyle.current.merge(textStyle ?: LocalTextStyle.current).copy(color = color),
content = content
)
}
}
@Stable
internal enum class ActionBarType { LARGE, MIDDLE }
object ActionBar {
val colors: ActionBarColors
@Composable
@ReadOnlyComposable
get() = defaultActionBarColors()
val style: ActionBarStyle
@Composable
@ReadOnlyComposable
get() = defaultActionBarStyle()
}
private val LocalActionBarType = compositionLocalOf { ActionBarType.LARGE }
@Composable
@ReadOnlyComposable
private fun defaultActionBarColors() = ActionBarColors(
titleTextColor = LocalColors.current.textPrimary,
subTextColor = LocalColors.current.textSecondary,
actionContentColor = LocalColors.current.textPrimary
)
@Composable
@ReadOnlyComposable
private fun defaultActionBarStyle() = ActionBarStyle(
padding = when {
LocalInSurface.current || LocalInAreaBox.current ->
PaddingValues(vertical = LocalSizes.current.spacingPrimary)
else -> PaddingValues(LocalSizes.current.spacingPrimary)
},
contentSpacing = LocalSizes.current.spacingSecondary,
titleTextStyle = when (LocalActionBarType.current) {
ActionBarType.LARGE -> LocalTypography.current.titlePrimary
ActionBarType.MIDDLE -> LocalTypography.current.titleSecondary
},
subTextStyle = LocalTypography.current.subtitle,
actionContentMaxWidth = DefaultActionContentMaxWidth,
actionIconPadding = LocalSizes.current.spacingTertiary,
actionIconMaxSize = when (LocalActionBarType.current) {
ActionBarType.LARGE -> LocalSizes.current.iconSizePrimary
ActionBarType.MIDDLE -> LocalSizes.current.iconSizeSecondary
}
)
private val DefaultActionContentMaxWidth = 170.dp