288 lines
12 KiB
Kotlin
Raw Normal View History

package eu.kanade.presentation.components
2022-06-25 22:03:48 +07:00
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.ZeroCornerSize
2022-06-25 22:03:48 +07:00
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BookmarkAdd
import androidx.compose.material.icons.filled.BookmarkRemove
import androidx.compose.material.icons.filled.DoneAll
import androidx.compose.material.icons.filled.RemoveDone
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.Label
2022-06-25 22:03:48 +07:00
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
@Composable
fun MangaBottomActionMenu(
visible: Boolean,
modifier: Modifier = Modifier,
onBookmarkClicked: (() -> Unit)? = null,
onRemoveBookmarkClicked: (() -> Unit)? = null,
onMarkAsReadClicked: (() -> Unit)? = null,
onMarkAsUnreadClicked: (() -> Unit)? = null,
onMarkPreviousAsReadClicked: (() -> Unit)? = null,
onDownloadClicked: (() -> Unit)? = null,
onDeleteClicked: (() -> Unit)? = null,
2022-06-25 22:03:48 +07:00
) {
AnimatedVisibility(
visible = visible,
enter = expandVertically(expandFrom = Alignment.Bottom),
exit = shrinkVertically(shrinkTowards = Alignment.Bottom),
) {
val scope = rememberCoroutineScope()
Surface(
modifier = modifier,
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
2022-06-25 22:03:48 +07:00
tonalElevation = 3.dp,
) {
val haptic = LocalHapticFeedback.current
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
var resetJob: Job? = remember { null }
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
(0 until 7).forEach { i -> confirm[i] = i == toConfirmIndex }
resetJob?.cancel()
resetJob = scope.launch {
delay(1000)
if (isActive) confirm[toConfirmIndex] = false
}
}
Row(
modifier = Modifier
.navigationBarsPadding()
.padding(horizontal = 8.dp, vertical = 12.dp),
) {
if (onBookmarkClicked != null) {
Button(
title = stringResource(R.string.action_bookmark),
2022-06-25 22:03:48 +07:00
icon = Icons.Default.BookmarkAdd,
toConfirm = confirm[0],
onLongClick = { onLongClickItem(0) },
onClick = onBookmarkClicked,
)
}
if (onRemoveBookmarkClicked != null) {
Button(
title = stringResource(R.string.action_remove_bookmark),
2022-06-25 22:03:48 +07:00
icon = Icons.Default.BookmarkRemove,
toConfirm = confirm[1],
onLongClick = { onLongClickItem(1) },
onClick = onRemoveBookmarkClicked,
)
}
if (onMarkAsReadClicked != null) {
Button(
title = stringResource(R.string.action_mark_as_read),
2022-06-25 22:03:48 +07:00
icon = Icons.Default.DoneAll,
toConfirm = confirm[2],
onLongClick = { onLongClickItem(2) },
onClick = onMarkAsReadClicked,
)
}
if (onMarkAsUnreadClicked != null) {
Button(
title = stringResource(R.string.action_mark_as_unread),
2022-06-25 22:03:48 +07:00
icon = Icons.Default.RemoveDone,
toConfirm = confirm[3],
onLongClick = { onLongClickItem(3) },
onClick = onMarkAsUnreadClicked,
)
}
if (onMarkPreviousAsReadClicked != null) {
Button(
title = stringResource(R.string.action_mark_previous_as_read),
2022-06-25 22:03:48 +07:00
icon = ImageVector.vectorResource(id = R.drawable.ic_done_prev_24dp),
toConfirm = confirm[4],
onLongClick = { onLongClickItem(4) },
onClick = onMarkPreviousAsReadClicked,
)
}
if (onDownloadClicked != null) {
Button(
title = stringResource(R.string.action_download),
icon = Icons.Outlined.Download,
2022-06-25 22:03:48 +07:00
toConfirm = confirm[5],
onLongClick = { onLongClickItem(5) },
onClick = onDownloadClicked,
)
}
if (onDeleteClicked != null) {
Button(
title = stringResource(R.string.action_delete),
icon = Icons.Outlined.Delete,
2022-06-25 22:03:48 +07:00
toConfirm = confirm[6],
onLongClick = { onLongClickItem(6) },
onClick = onDeleteClicked,
)
}
}
}
}
}
@Composable
private fun RowScope.Button(
title: String,
icon: ImageVector,
toConfirm: Boolean,
onLongClick: () -> Unit,
onClick: () -> Unit,
) {
val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f)
Column(
modifier = Modifier
.size(48.dp)
.weight(animatedWeight)
.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
onLongClick = onLongClick,
onClick = onClick,
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
imageVector = icon,
contentDescription = title,
)
AnimatedVisibility(
visible = toConfirm,
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
) {
Text(
text = title,
overflow = TextOverflow.Visible,
maxLines = 1,
style = MaterialTheme.typography.labelSmall,
)
}
}
}
@Composable
fun LibraryBottomActionMenu(
visible: Boolean,
modifier: Modifier = Modifier,
onChangeCategoryClicked: (() -> Unit)?,
onMarkAsReadClicked: (() -> Unit)?,
onMarkAsUnreadClicked: (() -> Unit)?,
onDownloadClicked: (() -> Unit)?,
onDeleteClicked: (() -> Unit)?,
) {
AnimatedVisibility(
visible = visible,
enter = expandVertically(expandFrom = Alignment.Bottom),
exit = shrinkVertically(shrinkTowards = Alignment.Bottom),
) {
val scope = rememberCoroutineScope()
Surface(
modifier = modifier,
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
tonalElevation = 3.dp,
) {
val haptic = LocalHapticFeedback.current
val confirm = remember { mutableStateListOf(false, false, false, false, false) }
var resetJob: Job? = remember { null }
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
(0 until 5).forEach { i -> confirm[i] = i == toConfirmIndex }
resetJob?.cancel()
resetJob = scope.launch {
delay(1000)
if (isActive) confirm[toConfirmIndex] = false
}
}
Row(
modifier = Modifier
.navigationBarsPadding()
.padding(horizontal = 8.dp, vertical = 12.dp),
) {
if (onChangeCategoryClicked != null) {
Button(
title = stringResource(R.string.action_move_category),
icon = Icons.Outlined.Label,
toConfirm = confirm[0],
onLongClick = { onLongClickItem(0) },
onClick = onChangeCategoryClicked,
)
}
if (onMarkAsReadClicked != null) {
Button(
title = stringResource(R.string.action_mark_as_read),
icon = Icons.Default.DoneAll,
toConfirm = confirm[1],
onLongClick = { onLongClickItem(1) },
onClick = onMarkAsReadClicked,
)
}
if (onMarkAsUnreadClicked != null) {
Button(
title = stringResource(R.string.action_mark_as_unread),
icon = Icons.Default.RemoveDone,
toConfirm = confirm[2],
onLongClick = { onLongClickItem(2) },
onClick = onMarkAsUnreadClicked,
)
}
if (onDownloadClicked != null) {
Button(
title = stringResource(R.string.action_download),
icon = Icons.Outlined.Download,
toConfirm = confirm[3],
onLongClick = { onLongClickItem(3) },
onClick = onDownloadClicked,
)
}
if (onDeleteClicked != null) {
Button(
title = stringResource(R.string.action_delete),
icon = Icons.Outlined.Delete,
toConfirm = confirm[4],
onLongClick = { onLongClickItem(4) },
onClick = onDeleteClicked,
)
}
}
}
}
}