tachiyomi/app/src/main/java/eu/kanade/presentation/components/AppBar.kt

279 lines
8.7 KiB
Kotlin
Raw Normal View History

2022-04-24 16:22:22 +02:00
package eu.kanade.presentation.components
import androidx.compose.animation.AnimatedVisibility
2022-04-24 16:22:22 +02:00
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.text.BasicTextField
2022-04-24 16:22:22 +02:00
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
2022-04-24 16:22:22 +02:00
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close
2022-04-24 16:22:22 +02:00
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallTopAppBar
2022-04-24 16:22:22 +02:00
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.surfaceColorAtElevation
2022-04-24 16:22:22 +02:00
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
2022-04-24 16:22:22 +02:00
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.SolidColor
2022-04-24 16:22:22 +02:00
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
2022-04-24 16:22:22 +02:00
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
2022-04-24 16:22:22 +02:00
import eu.kanade.tachiyomi.R
import kotlinx.coroutines.delay
2022-04-24 16:22:22 +02:00
@Composable
fun AppBar(
modifier: Modifier = Modifier,
// Text
title: String?,
subtitle: String? = null,
// Up button
navigateUp: (() -> Unit)? = null,
navigationIcon: ImageVector = Icons.Default.ArrowBack,
// Menu
actions: @Composable RowScope.() -> Unit = {},
// Action mode
actionModeCounter: Int = 0,
onCancelActionMode: () -> Unit = {},
actionModeActions: @Composable RowScope.() -> Unit = {},
// Banners
downloadedOnlyMode: Boolean = false,
incognitoMode: Boolean = false,
scrollBehavior: TopAppBarScrollBehavior? = null,
) {
val isActionMode by derivedStateOf { actionModeCounter > 0 }
AppBar(
modifier = modifier,
titleContent = {
if (isActionMode) {
AppBarTitle(actionModeCounter.toString())
} else {
AppBarTitle(title, subtitle)
}
},
navigateUp = navigateUp,
navigationIcon = navigationIcon,
actions = {
if (isActionMode) {
actionModeActions()
} else {
actions()
}
},
isActionMode = isActionMode,
onCancelActionMode = onCancelActionMode,
downloadedOnlyMode = downloadedOnlyMode,
incognitoMode = incognitoMode,
scrollBehavior = scrollBehavior,
)
}
@Composable
fun AppBar(
modifier: Modifier = Modifier,
// Title
titleContent: @Composable () -> Unit,
// Up button
navigateUp: (() -> Unit)? = null,
navigationIcon: ImageVector = Icons.Default.ArrowBack,
// Menu
actions: @Composable RowScope.() -> Unit = {},
// Action mode
isActionMode: Boolean = false,
onCancelActionMode: () -> Unit = {},
// Banners
downloadedOnlyMode: Boolean = false,
incognitoMode: Boolean = false,
scrollBehavior: TopAppBarScrollBehavior? = null,
) {
Column(
modifier = modifier,
) {
SmallTopAppBar(
navigationIcon = {
if (isActionMode) {
IconButton(onClick = onCancelActionMode) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.action_cancel),
)
}
} else {
navigateUp?.let {
IconButton(onClick = it) {
Icon(
imageVector = navigationIcon,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
}
}
}
},
title = titleContent,
actions = actions,
windowInsets = WindowInsets.statusBars,
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
elevation = if (isActionMode) 3.dp else 0.dp,
),
),
scrollBehavior = scrollBehavior,
)
if (downloadedOnlyMode) {
DownloadedOnlyModeBanner()
}
if (incognitoMode) {
IncognitoModeBanner()
}
}
}
2022-04-24 16:22:22 +02:00
@Composable
fun AppBarTitle(
title: String?,
subtitle: String? = null,
) {
Column {
title?.let {
Text(
text = it,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
subtitle?.let {
Text(
text = it,
2022-04-24 20:39:51 +02:00
style = MaterialTheme.typography.bodyMedium,
2022-04-24 16:22:22 +02:00
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@Composable
fun AppBarActions(
actions: List<AppBar.AppBarAction>,
) {
var showMenu by remember { mutableStateOf(false) }
actions.filterIsInstance<AppBar.Action>().map {
IconButton(
onClick = it.onClick,
2022-04-24 20:39:51 +02:00
enabled = it.enabled,
2022-04-24 16:22:22 +02:00
) {
Icon(
imageVector = it.icon,
contentDescription = it.title,
)
}
}
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
if (overflowActions.isNotEmpty()) {
IconButton(onClick = { showMenu = !showMenu }) {
Icon(Icons.Default.MoreVert, contentDescription = stringResource(R.string.label_more))
}
DropdownMenu(
expanded = showMenu,
2022-04-24 20:39:51 +02:00
onDismissRequest = { showMenu = false },
2022-04-24 16:22:22 +02:00
) {
overflowActions.map {
DropdownMenuItem(
onClick = {
it.onClick()
showMenu = false
},
text = { Text(it.title, fontWeight = FontWeight.Normal) },
2022-04-24 16:22:22 +02:00
)
}
}
}
}
@Composable
fun SearchToolbar(
searchQuery: String,
onChangeSearchQuery: (String) -> Unit,
onClickCloseSearch: () -> Unit,
onClickResetSearch: () -> Unit,
incognitoMode: Boolean = false,
downloadedOnlyMode: Boolean = false,
scrollBehavior: TopAppBarScrollBehavior? = null,
) {
val focusRequester = remember { FocusRequester.Default }
AppBar(
titleContent = {
BasicTextField(
value = searchQuery,
onValueChange = onChangeSearchQuery,
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
textStyle = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onBackground),
singleLine = true,
cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground),
)
},
navigationIcon = Icons.Outlined.ArrowBack,
navigateUp = onClickCloseSearch,
actions = {
AnimatedVisibility(visible = searchQuery.isNotEmpty()) {
IconButton(onClick = onClickResetSearch) {
Icon(Icons.Outlined.Close, contentDescription = stringResource(R.string.action_reset))
}
}
},
isActionMode = false,
downloadedOnlyMode = downloadedOnlyMode,
incognitoMode = incognitoMode,
scrollBehavior = scrollBehavior,
)
LaunchedEffect(focusRequester) {
// TODO: https://issuetracker.google.com/issues/204502668
delay(100)
focusRequester.requestFocus()
}
}
sealed interface AppBar {
sealed interface AppBarAction
2022-04-24 16:22:22 +02:00
data class Action(
val title: String,
val icon: ImageVector,
val onClick: () -> Unit,
2022-04-24 20:39:51 +02:00
val enabled: Boolean = true,
2022-04-24 16:22:22 +02:00
) : AppBarAction
data class OverflowAction(
val title: String,
val onClick: () -> Unit,
) : AppBarAction
}