Bump Compose M3 to 1.0.0-beta01 (#7867)
This commit is contained in:
parent
aab5f083db
commit
655fa25b51
7 changed files with 520 additions and 49 deletions
|
@ -2,7 +2,8 @@ package eu.kanade.presentation.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.statusBarsPadding
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
@ -15,6 +16,7 @@ import androidx.compose.material3.SmallTopAppBar
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -22,12 +24,11 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawBehind
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -97,14 +98,10 @@ fun AppBar(
|
||||||
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
) {
|
) {
|
||||||
val scrollFraction = if (isActionMode) 1f else scrollBehavior?.state?.overlappedFraction ?: 0f
|
|
||||||
val backgroundColor by TopAppBarDefaults.smallTopAppBarColors().containerColor(scrollFraction)
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.drawBehind { drawRect(backgroundColor) },
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
SmallTopAppBar(
|
SmallTopAppBar(
|
||||||
modifier = Modifier.statusBarsPadding(),
|
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
if (isActionMode) {
|
if (isActionMode) {
|
||||||
IconButton(onClick = onCancelActionMode) {
|
IconButton(onClick = onCancelActionMode) {
|
||||||
|
@ -126,10 +123,11 @@ fun AppBar(
|
||||||
},
|
},
|
||||||
title = titleContent,
|
title = titleContent,
|
||||||
actions = actions,
|
actions = actions,
|
||||||
// Background handled by parent
|
windowInsets = WindowInsets.statusBars,
|
||||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||||
containerColor = Color.Transparent,
|
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
scrolledContainerColor = Color.Transparent,
|
elevation = if (isActionMode) 3.dp else 0.dp,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,26 +1,45 @@
|
||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.VectorConverter
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.interaction.FocusInteraction
|
||||||
|
import androidx.compose.foundation.interaction.HoverInteraction
|
||||||
|
import androidx.compose.foundation.interaction.Interaction
|
||||||
|
import androidx.compose.foundation.interaction.InteractionSource
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.PressInteraction
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.ButtonColors
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.ButtonElevation
|
import androidx.compose.material3.ElevatedButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProvideTextStyle
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.presentation.util.animateElevation
|
||||||
|
import androidx.compose.material3.ButtonDefaults as M3ButtonDefaults
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextButton(
|
fun TextButton(
|
||||||
|
@ -30,10 +49,15 @@ fun TextButton(
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
elevation: ButtonElevation? = null,
|
elevation: ButtonElevation? = null,
|
||||||
shape: Shape = ButtonDefaults.textShape,
|
shape: Shape = M3ButtonDefaults.textShape,
|
||||||
border: BorderStroke? = null,
|
border: BorderStroke? = null,
|
||||||
colors: ButtonColors = ButtonDefaults.textButtonColors(),
|
colors: ButtonColors = ButtonColors(
|
||||||
contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding,
|
containerColor = Color.Transparent,
|
||||||
|
contentColor = MaterialTheme.colorScheme.primary,
|
||||||
|
disabledContainerColor = Color.Transparent,
|
||||||
|
disabledContentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),
|
||||||
|
),
|
||||||
|
contentPadding: PaddingValues = M3ButtonDefaults.TextButtonContentPadding,
|
||||||
content: @Composable RowScope.() -> Unit,
|
content: @Composable RowScope.() -> Unit,
|
||||||
) =
|
) =
|
||||||
Button(
|
Button(
|
||||||
|
@ -58,10 +82,10 @@ fun Button(
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
|
elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
|
||||||
shape: Shape = ButtonDefaults.textShape,
|
shape: Shape = M3ButtonDefaults.textShape,
|
||||||
border: BorderStroke? = null,
|
border: BorderStroke? = null,
|
||||||
colors: ButtonColors = ButtonDefaults.buttonColors(),
|
colors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||||
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
|
contentPadding: PaddingValues = M3ButtonDefaults.ContentPadding,
|
||||||
content: @Composable RowScope.() -> Unit,
|
content: @Composable RowScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
val containerColor = colors.containerColor(enabled).value
|
val containerColor = colors.containerColor(enabled).value
|
||||||
|
@ -86,8 +110,8 @@ fun Button(
|
||||||
ProvideTextStyle(value = MaterialTheme.typography.labelLarge) {
|
ProvideTextStyle(value = MaterialTheme.typography.labelLarge) {
|
||||||
Row(
|
Row(
|
||||||
Modifier.defaultMinSize(
|
Modifier.defaultMinSize(
|
||||||
minWidth = ButtonDefaults.MinWidth,
|
minWidth = M3ButtonDefaults.MinWidth,
|
||||||
minHeight = ButtonDefaults.MinHeight,
|
minHeight = M3ButtonDefaults.MinHeight,
|
||||||
)
|
)
|
||||||
.padding(contentPadding),
|
.padding(contentPadding),
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
@ -98,3 +122,255 @@ fun Button(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object ButtonDefaults {
|
||||||
|
/**
|
||||||
|
* Creates a [ButtonColors] that represents the default container and content colors used in a
|
||||||
|
* [Button].
|
||||||
|
*
|
||||||
|
* @param containerColor the container color of this [Button] when enabled.
|
||||||
|
* @param contentColor the content color of this [Button] when enabled.
|
||||||
|
* @param disabledContainerColor the container color of this [Button] when not enabled.
|
||||||
|
* @param disabledContentColor the content color of this [Button] when not enabled.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun buttonColors(
|
||||||
|
containerColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
contentColor: Color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
disabledContainerColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),
|
||||||
|
): ButtonColors = ButtonColors(
|
||||||
|
containerColor = containerColor,
|
||||||
|
contentColor = contentColor,
|
||||||
|
disabledContainerColor = disabledContainerColor,
|
||||||
|
disabledContentColor = disabledContentColor,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a [ButtonElevation] that will animate between the provided values according to the
|
||||||
|
* Material specification for a [Button].
|
||||||
|
*
|
||||||
|
* @param defaultElevation the elevation used when the [Button] is enabled, and has no other
|
||||||
|
* [Interaction]s.
|
||||||
|
* @param pressedElevation the elevation used when this [Button] is enabled and pressed.
|
||||||
|
* @param focusedElevation the elevation used when the [Button] is enabled and focused.
|
||||||
|
* @param hoveredElevation the elevation used when the [Button] is enabled and hovered.
|
||||||
|
* @param disabledElevation the elevation used when the [Button] is not enabled.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun buttonElevation(
|
||||||
|
defaultElevation: Dp = 0.dp,
|
||||||
|
pressedElevation: Dp = 0.dp,
|
||||||
|
focusedElevation: Dp = 0.dp,
|
||||||
|
hoveredElevation: Dp = 1.dp,
|
||||||
|
disabledElevation: Dp = 0.dp,
|
||||||
|
): ButtonElevation = ButtonElevation(
|
||||||
|
defaultElevation = defaultElevation,
|
||||||
|
pressedElevation = pressedElevation,
|
||||||
|
focusedElevation = focusedElevation,
|
||||||
|
hoveredElevation = hoveredElevation,
|
||||||
|
disabledElevation = disabledElevation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the elevation for a button in different states.
|
||||||
|
*
|
||||||
|
* - See [M3ButtonDefaults.buttonElevation] for the default elevation used in a [Button].
|
||||||
|
* - See [M3ButtonDefaults.elevatedButtonElevation] for the default elevation used in a
|
||||||
|
* [ElevatedButton].
|
||||||
|
*/
|
||||||
|
@Stable
|
||||||
|
class ButtonElevation internal constructor(
|
||||||
|
private val defaultElevation: Dp,
|
||||||
|
private val pressedElevation: Dp,
|
||||||
|
private val focusedElevation: Dp,
|
||||||
|
private val hoveredElevation: Dp,
|
||||||
|
private val disabledElevation: Dp,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Represents the tonal elevation used in a button, depending on its [enabled] state and
|
||||||
|
* [interactionSource]. This should typically be the same value as the [shadowElevation].
|
||||||
|
*
|
||||||
|
* Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
|
||||||
|
* When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
|
||||||
|
* color in light theme and lighter color in dark theme.
|
||||||
|
*
|
||||||
|
* See [shadowElevation] which controls the elevation of the shadow drawn around the button.
|
||||||
|
*
|
||||||
|
* @param enabled whether the button is enabled
|
||||||
|
* @param interactionSource the [InteractionSource] for this button
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun tonalElevation(enabled: Boolean, interactionSource: InteractionSource): State<Dp> {
|
||||||
|
return animateElevation(enabled = enabled, interactionSource = interactionSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the shadow elevation used in a button, depending on its [enabled] state and
|
||||||
|
* [interactionSource]. This should typically be the same value as the [tonalElevation].
|
||||||
|
*
|
||||||
|
* Shadow elevation is used to apply a shadow around the button to give it higher emphasis.
|
||||||
|
*
|
||||||
|
* See [tonalElevation] which controls the elevation with a color shift to the surface.
|
||||||
|
*
|
||||||
|
* @param enabled whether the button is enabled
|
||||||
|
* @param interactionSource the [InteractionSource] for this button
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun shadowElevation(
|
||||||
|
enabled: Boolean,
|
||||||
|
interactionSource: InteractionSource,
|
||||||
|
): State<Dp> {
|
||||||
|
return animateElevation(enabled = enabled, interactionSource = interactionSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun animateElevation(
|
||||||
|
enabled: Boolean,
|
||||||
|
interactionSource: InteractionSource,
|
||||||
|
): State<Dp> {
|
||||||
|
val interactions = remember { mutableStateListOf<Interaction>() }
|
||||||
|
LaunchedEffect(interactionSource) {
|
||||||
|
interactionSource.interactions.collect { interaction ->
|
||||||
|
when (interaction) {
|
||||||
|
is HoverInteraction.Enter -> {
|
||||||
|
interactions.add(interaction)
|
||||||
|
}
|
||||||
|
is HoverInteraction.Exit -> {
|
||||||
|
interactions.remove(interaction.enter)
|
||||||
|
}
|
||||||
|
is FocusInteraction.Focus -> {
|
||||||
|
interactions.add(interaction)
|
||||||
|
}
|
||||||
|
is FocusInteraction.Unfocus -> {
|
||||||
|
interactions.remove(interaction.focus)
|
||||||
|
}
|
||||||
|
is PressInteraction.Press -> {
|
||||||
|
interactions.add(interaction)
|
||||||
|
}
|
||||||
|
is PressInteraction.Release -> {
|
||||||
|
interactions.remove(interaction.press)
|
||||||
|
}
|
||||||
|
is PressInteraction.Cancel -> {
|
||||||
|
interactions.remove(interaction.press)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val interaction = interactions.lastOrNull()
|
||||||
|
|
||||||
|
val target =
|
||||||
|
if (!enabled) {
|
||||||
|
disabledElevation
|
||||||
|
} else {
|
||||||
|
when (interaction) {
|
||||||
|
is PressInteraction.Press -> pressedElevation
|
||||||
|
is HoverInteraction.Enter -> hoveredElevation
|
||||||
|
is FocusInteraction.Focus -> focusedElevation
|
||||||
|
else -> defaultElevation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val animatable = remember { Animatable(target, Dp.VectorConverter) }
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
// No transition when moving to a disabled state
|
||||||
|
LaunchedEffect(target) { animatable.snapTo(target) }
|
||||||
|
} else {
|
||||||
|
LaunchedEffect(target) {
|
||||||
|
val lastInteraction = when (animatable.targetValue) {
|
||||||
|
pressedElevation -> PressInteraction.Press(Offset.Zero)
|
||||||
|
hoveredElevation -> HoverInteraction.Enter()
|
||||||
|
focusedElevation -> FocusInteraction.Focus()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
animatable.animateElevation(
|
||||||
|
from = lastInteraction,
|
||||||
|
to = interaction,
|
||||||
|
target = target,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return animatable.asState()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || other !is ButtonElevation) return false
|
||||||
|
|
||||||
|
if (defaultElevation != other.defaultElevation) return false
|
||||||
|
if (pressedElevation != other.pressedElevation) return false
|
||||||
|
if (focusedElevation != other.focusedElevation) return false
|
||||||
|
if (hoveredElevation != other.hoveredElevation) return false
|
||||||
|
if (disabledElevation != other.disabledElevation) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = defaultElevation.hashCode()
|
||||||
|
result = 31 * result + pressedElevation.hashCode()
|
||||||
|
result = 31 * result + focusedElevation.hashCode()
|
||||||
|
result = 31 * result + hoveredElevation.hashCode()
|
||||||
|
result = 31 * result + disabledElevation.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the container and content colors used in a button in different states.
|
||||||
|
*
|
||||||
|
* - See [M3ButtonDefaults.buttonColors] for the default colors used in a [Button].
|
||||||
|
* - See [M3ButtonDefaults.elevatedButtonColors] for the default colors used in a [ElevatedButton].
|
||||||
|
* - See [M3ButtonDefaults.textButtonColors] for the default colors used in a [TextButton].
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class ButtonColors internal constructor(
|
||||||
|
private val containerColor: Color,
|
||||||
|
private val contentColor: Color,
|
||||||
|
private val disabledContainerColor: Color,
|
||||||
|
private val disabledContentColor: Color,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Represents the container color for this button, depending on [enabled].
|
||||||
|
*
|
||||||
|
* @param enabled whether the button is enabled
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun containerColor(enabled: Boolean): State<Color> {
|
||||||
|
return rememberUpdatedState(if (enabled) containerColor else disabledContainerColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the content color for this button, depending on [enabled].
|
||||||
|
*
|
||||||
|
* @param enabled whether the button is enabled
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun contentColor(enabled: Boolean): State<Color> {
|
||||||
|
return rememberUpdatedState(if (enabled) contentColor else disabledContentColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || other !is ButtonColors) return false
|
||||||
|
|
||||||
|
if (containerColor != other.containerColor) return false
|
||||||
|
if (contentColor != other.contentColor) return false
|
||||||
|
if (disabledContainerColor != other.disabledContainerColor) return false
|
||||||
|
if (disabledContentColor != other.disabledContentColor) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = containerColor.hashCode()
|
||||||
|
result = 31 * result + contentColor.hashCode()
|
||||||
|
result = 31 * result + disabledContainerColor.hashCode()
|
||||||
|
result = 31 * result + disabledContentColor.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,15 +23,20 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
|
import androidx.compose.material3.FilledIconButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButtonColors
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.IconButtonDefaults
|
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.OutlinedIconButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.util.minimumTouchTargetSize
|
import eu.kanade.presentation.util.minimumTouchTargetSize
|
||||||
|
@ -100,6 +105,88 @@ fun IconButton(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object IconButtonDefaults {
|
||||||
|
/**
|
||||||
|
* Creates a [IconButtonColors] that represents the default colors used in a [IconButton].
|
||||||
|
*
|
||||||
|
* @param containerColor the container color of this icon button when enabled.
|
||||||
|
* @param contentColor the content color of this icon button when enabled.
|
||||||
|
* @param disabledContainerColor the container color of this icon button when not enabled.
|
||||||
|
* @param disabledContentColor the content color of this icon button when not enabled.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun iconButtonColors(
|
||||||
|
containerColor: Color = Color.Transparent,
|
||||||
|
contentColor: Color = LocalContentColor.current,
|
||||||
|
disabledContainerColor: Color = Color.Transparent,
|
||||||
|
disabledContentColor: Color = contentColor.copy(alpha = 0.38f),
|
||||||
|
): IconButtonColors =
|
||||||
|
IconButtonColors(
|
||||||
|
containerColor = containerColor,
|
||||||
|
contentColor = contentColor,
|
||||||
|
disabledContainerColor = disabledContainerColor,
|
||||||
|
disabledContentColor = disabledContentColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
object IconButtonTokens {
|
object IconButtonTokens {
|
||||||
val StateLayerSize = 40.0.dp
|
val StateLayerSize = 40.0.dp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the container and content colors used in an icon button in different states.
|
||||||
|
*
|
||||||
|
* - See [IconButtonDefaults.filledIconButtonColors] and
|
||||||
|
* [IconButtonDefaults.filledTonalIconButtonColors] for the default colors used in a
|
||||||
|
* [FilledIconButton].
|
||||||
|
* - See [IconButtonDefaults.outlinedIconButtonColors] for the default colors used in an
|
||||||
|
* [OutlinedIconButton].
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class IconButtonColors internal constructor(
|
||||||
|
private val containerColor: Color,
|
||||||
|
private val contentColor: Color,
|
||||||
|
private val disabledContainerColor: Color,
|
||||||
|
private val disabledContentColor: Color,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Represents the container color for this icon button, depending on [enabled].
|
||||||
|
*
|
||||||
|
* @param enabled whether the icon button is enabled
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun containerColor(enabled: Boolean): State<Color> {
|
||||||
|
return rememberUpdatedState(if (enabled) containerColor else disabledContainerColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the content color for this icon button, depending on [enabled].
|
||||||
|
*
|
||||||
|
* @param enabled whether the icon button is enabled
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun contentColor(enabled: Boolean): State<Color> {
|
||||||
|
return rememberUpdatedState(if (enabled) contentColor else disabledContentColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || other !is IconButtonColors) return false
|
||||||
|
|
||||||
|
if (containerColor != other.containerColor) return false
|
||||||
|
if (contentColor != other.contentColor) return false
|
||||||
|
if (disabledContainerColor != other.disabledContainerColor) return false
|
||||||
|
if (disabledContentColor != other.disabledContentColor) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = containerColor.hashCode()
|
||||||
|
result = 31 * result + contentColor.hashCode()
|
||||||
|
result = 31 * result + disabledContainerColor.hashCode()
|
||||||
|
result = 31 * result + disabledContentColor.hashCode()
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,15 +17,12 @@ import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawBehind
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
@ -138,12 +135,7 @@ fun LibrarySelectionToolbar(
|
||||||
onClickSelectAll: () -> Unit,
|
onClickSelectAll: () -> Unit,
|
||||||
onClickInvertSelection: () -> Unit,
|
onClickInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val backgroundColor by TopAppBarDefaults.smallTopAppBarColors().containerColor(1f)
|
|
||||||
AppBar(
|
AppBar(
|
||||||
modifier = Modifier
|
|
||||||
.drawBehind {
|
|
||||||
drawRect(backgroundColor.copy(alpha = 1f))
|
|
||||||
},
|
|
||||||
titleContent = { Text(text = "${state.selection.size}") },
|
titleContent = { Text(text = "${state.selection.size}") },
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onClickSelectAll) {
|
IconButton(onClick = onClickSelectAll) {
|
||||||
|
|
|
@ -3,10 +3,7 @@ package eu.kanade.presentation.manga.components
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.foundation.layout.only
|
|
||||||
import androidx.compose.foundation.layout.systemBars
|
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
@ -18,19 +15,19 @@ import androidx.compose.material.icons.outlined.Share
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SmallTopAppBar
|
import androidx.compose.material3.SmallTopAppBar
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.drawBehind
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.DownloadedOnlyModeBanner
|
import eu.kanade.presentation.components.DownloadedOnlyModeBanner
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
import eu.kanade.presentation.components.IncognitoModeBanner
|
import eu.kanade.presentation.components.IncognitoModeBanner
|
||||||
|
@ -55,16 +52,11 @@ fun MangaAppBar(
|
||||||
onSelectAll: () -> Unit,
|
onSelectAll: () -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val isActionMode = actionModeCounter > 0
|
|
||||||
val backgroundAlpha = if (isActionMode) 1f else backgroundAlphaProvider()
|
|
||||||
val backgroundColor by TopAppBarDefaults.smallTopAppBarColors().containerColor(1f)
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.drawBehind {
|
modifier = modifier,
|
||||||
drawRect(backgroundColor.copy(alpha = backgroundAlpha))
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
|
val isActionMode = actionModeCounter > 0
|
||||||
SmallTopAppBar(
|
SmallTopAppBar(
|
||||||
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Top)),
|
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
text = if (isActionMode) actionModeCounter.toString() else title,
|
text = if (isActionMode) actionModeCounter.toString() else title,
|
||||||
|
@ -198,10 +190,11 @@ fun MangaAppBar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Background handled by parent
|
windowInsets = WindowInsets.statusBars,
|
||||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||||
containerColor = Color.Transparent,
|
containerColor = MaterialTheme.colorScheme
|
||||||
scrolledContainerColor = Color.Transparent,
|
.surfaceColorAtElevation(3.dp)
|
||||||
|
.copy(alpha = if (isActionMode) 1f else backgroundAlphaProvider()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
125
app/src/main/java/eu/kanade/presentation/util/Elevation.kt
Normal file
125
app/src/main/java/eu/kanade/presentation/util/Elevation.kt
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Straight copy from Compose M3 for Button fork
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.kanade.presentation.util
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.AnimationSpec
|
||||||
|
import androidx.compose.animation.core.CubicBezierEasing
|
||||||
|
import androidx.compose.animation.core.Easing
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.TweenSpec
|
||||||
|
import androidx.compose.foundation.interaction.DragInteraction
|
||||||
|
import androidx.compose.foundation.interaction.FocusInteraction
|
||||||
|
import androidx.compose.foundation.interaction.HoverInteraction
|
||||||
|
import androidx.compose.foundation.interaction.Interaction
|
||||||
|
import androidx.compose.foundation.interaction.PressInteraction
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animates the [Dp] value of [this] between [from] and [to] [Interaction]s, to [target]. The
|
||||||
|
* [AnimationSpec] used depends on the values for [from] and [to], see
|
||||||
|
* [ElevationDefaults.incomingAnimationSpecForInteraction] and
|
||||||
|
* [ElevationDefaults.outgoingAnimationSpecForInteraction] for more details.
|
||||||
|
*
|
||||||
|
* @param target the [Dp] target elevation for this component, corresponding to the elevation
|
||||||
|
* desired for the [to] state.
|
||||||
|
* @param from the previous [Interaction] that was used to calculate elevation. `null` if there
|
||||||
|
* was no previous [Interaction], such as when the component is in its default state.
|
||||||
|
* @param to the [Interaction] that this component is moving to, such as [PressInteraction.Press]
|
||||||
|
* when this component is being pressed. `null` if this component is moving back to its default
|
||||||
|
* state.
|
||||||
|
*/
|
||||||
|
internal suspend fun Animatable<Dp, *>.animateElevation(
|
||||||
|
target: Dp,
|
||||||
|
from: Interaction? = null,
|
||||||
|
to: Interaction? = null,
|
||||||
|
) {
|
||||||
|
val spec = when {
|
||||||
|
// Moving to a new state
|
||||||
|
to != null -> ElevationDefaults.incomingAnimationSpecForInteraction(to)
|
||||||
|
// Moving to default, from a previous state
|
||||||
|
from != null -> ElevationDefaults.outgoingAnimationSpecForInteraction(from)
|
||||||
|
// Loading the initial state, or moving back to the baseline state from a disabled /
|
||||||
|
// unknown state, so just snap to the final value.
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (spec != null) animateTo(target, spec) else snapTo(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains default [AnimationSpec]s used for animating elevation between different [Interaction]s.
|
||||||
|
*
|
||||||
|
* Typically you should use [animateElevation] instead, which uses these [AnimationSpec]s
|
||||||
|
* internally. [animateElevation] in turn is used by the defaults for cards and buttons.
|
||||||
|
*
|
||||||
|
* @see animateElevation
|
||||||
|
*/
|
||||||
|
private object ElevationDefaults {
|
||||||
|
/**
|
||||||
|
* Returns the [AnimationSpec]s used when animating elevation to [interaction], either from a
|
||||||
|
* previous [Interaction], or from the default state. If [interaction] is unknown, then
|
||||||
|
* returns `null`.
|
||||||
|
*
|
||||||
|
* @param interaction the [Interaction] that is being animated to
|
||||||
|
*/
|
||||||
|
fun incomingAnimationSpecForInteraction(interaction: Interaction): AnimationSpec<Dp>? {
|
||||||
|
return when (interaction) {
|
||||||
|
is PressInteraction.Press -> DefaultIncomingSpec
|
||||||
|
is DragInteraction.Start -> DefaultIncomingSpec
|
||||||
|
is HoverInteraction.Enter -> DefaultIncomingSpec
|
||||||
|
is FocusInteraction.Focus -> DefaultIncomingSpec
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the [AnimationSpec]s used when animating elevation away from [interaction], to the
|
||||||
|
* default state. If [interaction] is unknown, then returns `null`.
|
||||||
|
*
|
||||||
|
* @param interaction the [Interaction] that is being animated away from
|
||||||
|
*/
|
||||||
|
fun outgoingAnimationSpecForInteraction(interaction: Interaction): AnimationSpec<Dp>? {
|
||||||
|
return when (interaction) {
|
||||||
|
is PressInteraction.Press -> DefaultOutgoingSpec
|
||||||
|
is DragInteraction.Start -> DefaultOutgoingSpec
|
||||||
|
is HoverInteraction.Enter -> HoveredOutgoingSpec
|
||||||
|
is FocusInteraction.Focus -> DefaultOutgoingSpec
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val OutgoingSpecEasing: Easing = CubicBezierEasing(0.40f, 0.00f, 0.60f, 1.00f)
|
||||||
|
|
||||||
|
private val DefaultIncomingSpec = TweenSpec<Dp>(
|
||||||
|
durationMillis = 120,
|
||||||
|
easing = FastOutSlowInEasing,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val DefaultOutgoingSpec = TweenSpec<Dp>(
|
||||||
|
durationMillis = 150,
|
||||||
|
easing = OutgoingSpecEasing,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val HoveredOutgoingSpec = TweenSpec<Dp>(
|
||||||
|
durationMillis = 120,
|
||||||
|
easing = OutgoingSpecEasing,
|
||||||
|
)
|
|
@ -2,7 +2,7 @@
|
||||||
compiler = "1.3.0-rc02"
|
compiler = "1.3.0-rc02"
|
||||||
compose = "1.2.1"
|
compose = "1.2.1"
|
||||||
accompanist = "0.25.1"
|
accompanist = "0.25.1"
|
||||||
material3 = "1.0.0-alpha16"
|
material3 = "1.0.0-beta01"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
activity = "androidx.activity:activity-compose:1.6.0-beta01"
|
activity = "androidx.activity:activity-compose:1.6.0-beta01"
|
||||||
|
|
Reference in a new issue