123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- package eu.kanade.presentation.manga
- import androidx.compose.foundation.clickable
- import androidx.compose.foundation.layout.Arrangement
- import androidx.compose.foundation.layout.Box
- import androidx.compose.foundation.layout.Column
- import androidx.compose.foundation.layout.PaddingValues
- import androidx.compose.foundation.layout.Row
- import androidx.compose.foundation.layout.Spacer
- import androidx.compose.foundation.layout.fillMaxWidth
- import androidx.compose.foundation.layout.heightIn
- import androidx.compose.foundation.layout.padding
- import androidx.compose.foundation.layout.size
- import androidx.compose.foundation.layout.wrapContentSize
- import androidx.compose.foundation.rememberScrollState
- import androidx.compose.foundation.verticalScroll
- import androidx.compose.material.icons.Icons
- import androidx.compose.material.icons.filled.ArrowDownward
- import androidx.compose.material.icons.filled.ArrowUpward
- import androidx.compose.material.icons.filled.MoreVert
- import androidx.compose.material.icons.rounded.CheckBox
- import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
- import androidx.compose.material.icons.rounded.DisabledByDefault
- import androidx.compose.material3.AlertDialog
- import androidx.compose.material3.Checkbox
- import androidx.compose.material3.DropdownMenuItem
- import androidx.compose.material3.Icon
- import androidx.compose.material3.IconButton
- import androidx.compose.material3.MaterialTheme
- import androidx.compose.material3.RadioButton
- import androidx.compose.material3.Surface
- import androidx.compose.material3.Tab
- import androidx.compose.material3.TabRow
- import androidx.compose.material3.Text
- import androidx.compose.material3.TextButton
- import androidx.compose.runtime.Composable
- import androidx.compose.runtime.getValue
- import androidx.compose.runtime.mutableStateOf
- import androidx.compose.runtime.remember
- import androidx.compose.runtime.rememberCoroutineScope
- import androidx.compose.runtime.saveable.rememberSaveable
- import androidx.compose.runtime.setValue
- import androidx.compose.ui.Alignment
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.graphics.vector.ImageVector
- import androidx.compose.ui.layout.onSizeChanged
- import androidx.compose.ui.platform.LocalDensity
- import androidx.compose.ui.res.stringResource
- import androidx.compose.ui.unit.dp
- import androidx.compose.ui.util.fastForEachIndexed
- import eu.kanade.domain.manga.model.Manga
- import eu.kanade.domain.manga.model.TriStateFilter
- import eu.kanade.presentation.components.AdaptiveSheet
- import eu.kanade.presentation.components.Divider
- import eu.kanade.presentation.components.DropdownMenu
- import eu.kanade.presentation.components.HorizontalPager
- import eu.kanade.presentation.components.TabIndicator
- import eu.kanade.presentation.components.rememberPagerState
- import eu.kanade.presentation.theme.TachiyomiTheme
- import eu.kanade.presentation.util.ThemePreviews
- import eu.kanade.tachiyomi.R
- import kotlinx.coroutines.launch
- @Composable
- fun ChapterSettingsDialog(
- onDismissRequest: () -> Unit,
- manga: Manga? = null,
- onDownloadFilterChanged: (TriStateFilter) -> Unit,
- onUnreadFilterChanged: (TriStateFilter) -> Unit,
- onBookmarkedFilterChanged: (TriStateFilter) -> Unit,
- onSortModeChanged: (Long) -> Unit,
- onDisplayModeChanged: (Long) -> Unit,
- onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
- ) {
- AdaptiveSheet(
- onDismissRequest = onDismissRequest,
- ) { contentPadding ->
- ChapterSettingsDialogImpl(
- manga = manga,
- contentPadding = contentPadding,
- onDownloadFilterChanged = onDownloadFilterChanged,
- onUnreadFilterChanged = onUnreadFilterChanged,
- onBookmarkedFilterChanged = onBookmarkedFilterChanged,
- onSortModeChanged = onSortModeChanged,
- onDisplayModeChanged = onDisplayModeChanged,
- onSetAsDefault = onSetAsDefault,
- )
- }
- }
- @Composable
- private fun ChapterSettingsDialogImpl(
- manga: Manga? = null,
- contentPadding: PaddingValues = PaddingValues(),
- onDownloadFilterChanged: (TriStateFilter) -> Unit,
- onUnreadFilterChanged: (TriStateFilter) -> Unit,
- onBookmarkedFilterChanged: (TriStateFilter) -> Unit,
- onSortModeChanged: (Long) -> Unit,
- onDisplayModeChanged: (Long) -> Unit,
- onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
- ) {
- val scope = rememberCoroutineScope()
- val tabTitles = listOf(
- stringResource(R.string.action_filter),
- stringResource(R.string.action_sort),
- stringResource(R.string.action_display),
- )
- val pagerState = rememberPagerState()
- var showSetAsDefaultDialog by rememberSaveable { mutableStateOf(false) }
- if (showSetAsDefaultDialog) {
- SetAsDefaultDialog(
- onDismissRequest = { showSetAsDefaultDialog = false },
- onConfirmed = onSetAsDefault,
- )
- }
- Column {
- Row {
- TabRow(
- modifier = Modifier.weight(1f),
- selectedTabIndex = pagerState.currentPage,
- indicator = { TabIndicator(it[pagerState.currentPage]) },
- divider = {},
- ) {
- tabTitles.fastForEachIndexed { i, s ->
- val selected = pagerState.currentPage == i
- Tab(
- selected = selected,
- onClick = { scope.launch { pagerState.animateScrollToPage(i) } },
- text = {
- Text(
- text = s,
- color = if (selected) {
- MaterialTheme.colorScheme.primary
- } else {
- MaterialTheme.colorScheme.onSurfaceVariant
- },
- )
- },
- )
- }
- }
- MoreMenu(onSetAsDefault = { showSetAsDefaultDialog = true })
- }
- Divider()
- val density = LocalDensity.current
- var largestHeight by rememberSaveable { mutableStateOf(0f) }
- HorizontalPager(
- modifier = Modifier.heightIn(min = largestHeight.dp),
- count = tabTitles.size,
- state = pagerState,
- verticalAlignment = Alignment.Top,
- ) { page ->
- Box(
- modifier = Modifier.onSizeChanged {
- with(density) {
- val heightDp = it.height.toDp()
- if (heightDp.value > largestHeight) {
- largestHeight = heightDp.value
- }
- }
- },
- ) {
- when (page) {
- 0 -> {
- val forceDownloaded = manga?.forceDownloaded() == true
- FilterPage(
- contentPadding = contentPadding,
- downloadFilter = if (forceDownloaded) {
- TriStateFilter.ENABLED_NOT
- } else {
- manga?.downloadedFilter
- } ?: TriStateFilter.DISABLED,
- onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { forceDownloaded },
- unreadFilter = manga?.unreadFilter ?: TriStateFilter.DISABLED,
- onUnreadFilterChanged = onUnreadFilterChanged,
- bookmarkedFilter = manga?.bookmarkedFilter ?: TriStateFilter.DISABLED,
- onBookmarkedFilterChanged = onBookmarkedFilterChanged,
- )
- }
- 1 -> SortPage(
- contentPadding = contentPadding,
- sortingMode = manga?.sorting ?: 0,
- sortDescending = manga?.sortDescending() ?: false,
- onItemSelected = onSortModeChanged,
- )
- 2 -> DisplayPage(
- contentPadding = contentPadding,
- displayMode = manga?.displayMode ?: 0,
- onItemSelected = onDisplayModeChanged,
- )
- }
- }
- }
- }
- }
- @Composable
- private fun SetAsDefaultDialog(
- onDismissRequest: () -> Unit,
- onConfirmed: (optionalChecked: Boolean) -> Unit,
- ) {
- var optionalChecked by rememberSaveable { mutableStateOf(false) }
- AlertDialog(
- onDismissRequest = onDismissRequest,
- title = { Text(text = stringResource(R.string.chapter_settings)) },
- text = {
- Column(
- verticalArrangement = Arrangement.spacedBy(12.dp),
- ) {
- Text(text = stringResource(R.string.confirm_set_chapter_settings))
- Row(
- modifier = Modifier
- .clickable { optionalChecked = !optionalChecked }
- .padding(vertical = 8.dp)
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(12.dp),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Checkbox(
- checked = optionalChecked,
- onCheckedChange = null,
- )
- Text(text = stringResource(R.string.also_set_chapter_settings_for_library))
- }
- }
- },
- dismissButton = {
- TextButton(onClick = onDismissRequest) {
- Text(text = stringResource(android.R.string.cancel))
- }
- },
- confirmButton = {
- TextButton(
- onClick = {
- onConfirmed(optionalChecked)
- },
- ) {
- Text(text = stringResource(android.R.string.ok))
- }
- },
- )
- }
- @Composable
- private fun MoreMenu(
- onSetAsDefault: () -> Unit,
- ) {
- var expanded by remember { mutableStateOf(false) }
- Box(modifier = Modifier.wrapContentSize(Alignment.TopStart)) {
- IconButton(onClick = { expanded = true }) {
- Icon(
- imageVector = Icons.Default.MoreVert,
- contentDescription = stringResource(R.string.label_more),
- )
- }
- DropdownMenu(
- expanded = expanded,
- onDismissRequest = { expanded = false },
- ) {
- DropdownMenuItem(
- text = { Text(stringResource(R.string.set_chapter_settings_as_default)) },
- onClick = {
- onSetAsDefault()
- expanded = false
- },
- )
- }
- }
- }
- @Composable
- private fun FilterPage(
- contentPadding: PaddingValues,
- downloadFilter: TriStateFilter,
- onDownloadFilterChanged: ((TriStateFilter) -> Unit)?,
- unreadFilter: TriStateFilter,
- onUnreadFilterChanged: (TriStateFilter) -> Unit,
- bookmarkedFilter: TriStateFilter,
- onBookmarkedFilterChanged: (TriStateFilter) -> Unit,
- ) {
- Column(
- modifier = Modifier
- .padding(vertical = VerticalPadding)
- .padding(contentPadding)
- .verticalScroll(rememberScrollState()),
- ) {
- FilterPageItem(
- label = stringResource(R.string.label_downloaded),
- state = downloadFilter,
- onClick = onDownloadFilterChanged,
- )
- FilterPageItem(
- label = stringResource(R.string.action_filter_unread),
- state = unreadFilter,
- onClick = onUnreadFilterChanged,
- )
- FilterPageItem(
- label = stringResource(R.string.action_filter_bookmarked),
- state = bookmarkedFilter,
- onClick = onBookmarkedFilterChanged,
- )
- }
- }
- @Composable
- private fun FilterPageItem(
- label: String,
- state: TriStateFilter,
- onClick: ((TriStateFilter) -> Unit)?,
- ) {
- Row(
- modifier = Modifier
- .clickable(
- enabled = onClick != null,
- onClick = {
- when (state) {
- TriStateFilter.DISABLED -> onClick?.invoke(TriStateFilter.ENABLED_IS)
- TriStateFilter.ENABLED_IS -> onClick?.invoke(TriStateFilter.ENABLED_NOT)
- TriStateFilter.ENABLED_NOT -> onClick?.invoke(TriStateFilter.DISABLED)
- }
- },
- )
- .fillMaxWidth()
- .padding(horizontal = HorizontalPadding, vertical = 12.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(24.dp),
- ) {
- Icon(
- imageVector = when (state) {
- TriStateFilter.DISABLED -> Icons.Rounded.CheckBoxOutlineBlank
- TriStateFilter.ENABLED_IS -> Icons.Rounded.CheckBox
- TriStateFilter.ENABLED_NOT -> Icons.Rounded.DisabledByDefault
- },
- contentDescription = null,
- tint = if (state == TriStateFilter.DISABLED) {
- MaterialTheme.colorScheme.onSurfaceVariant
- } else {
- MaterialTheme.colorScheme.primary
- },
- )
- Text(
- text = label,
- style = MaterialTheme.typography.bodyMedium,
- )
- }
- }
- @Composable
- private fun SortPage(
- contentPadding: PaddingValues,
- sortingMode: Long,
- sortDescending: Boolean,
- onItemSelected: (Long) -> Unit,
- ) {
- Column(
- modifier = Modifier
- .padding(contentPadding)
- .padding(vertical = VerticalPadding)
- .verticalScroll(rememberScrollState()),
- ) {
- val arrowIcon = if (sortDescending) {
- Icons.Default.ArrowDownward
- } else {
- Icons.Default.ArrowUpward
- }
- SortPageItem(
- label = stringResource(R.string.sort_by_source),
- statusIcon = arrowIcon.takeIf { sortingMode == Manga.CHAPTER_SORTING_SOURCE },
- onClick = { onItemSelected(Manga.CHAPTER_SORTING_SOURCE) },
- )
- SortPageItem(
- label = stringResource(R.string.sort_by_number),
- statusIcon = arrowIcon.takeIf { sortingMode == Manga.CHAPTER_SORTING_NUMBER },
- onClick = { onItemSelected(Manga.CHAPTER_SORTING_NUMBER) },
- )
- SortPageItem(
- label = stringResource(R.string.sort_by_upload_date),
- statusIcon = arrowIcon.takeIf { sortingMode == Manga.CHAPTER_SORTING_UPLOAD_DATE },
- onClick = { onItemSelected(Manga.CHAPTER_SORTING_UPLOAD_DATE) },
- )
- }
- }
- @Composable
- private fun SortPageItem(
- label: String,
- statusIcon: ImageVector?,
- onClick: () -> Unit,
- ) {
- Row(
- modifier = Modifier
- .clickable(onClick = onClick)
- .fillMaxWidth()
- .padding(horizontal = HorizontalPadding, vertical = 12.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(24.dp),
- ) {
- if (statusIcon != null) {
- Icon(
- imageVector = statusIcon,
- contentDescription = null,
- tint = MaterialTheme.colorScheme.primary,
- )
- } else {
- Spacer(modifier = Modifier.size(24.dp))
- }
- Text(
- text = label,
- style = MaterialTheme.typography.bodyMedium,
- )
- }
- }
- @Composable
- private fun DisplayPage(
- contentPadding: PaddingValues,
- displayMode: Long,
- onItemSelected: (Long) -> Unit,
- ) {
- Column(
- modifier = Modifier
- .padding(contentPadding)
- .padding(vertical = VerticalPadding)
- .verticalScroll(rememberScrollState()),
- ) {
- DisplayPageItem(
- label = stringResource(R.string.show_title),
- selected = displayMode == Manga.CHAPTER_DISPLAY_NAME,
- onClick = { onItemSelected(Manga.CHAPTER_DISPLAY_NAME) },
- )
- DisplayPageItem(
- label = stringResource(R.string.show_chapter_number),
- selected = displayMode == Manga.CHAPTER_DISPLAY_NUMBER,
- onClick = { onItemSelected(Manga.CHAPTER_DISPLAY_NUMBER) },
- )
- }
- }
- @Composable
- private fun DisplayPageItem(
- label: String,
- selected: Boolean,
- onClick: () -> Unit,
- ) {
- Row(
- modifier = Modifier
- .clickable(onClick = onClick)
- .fillMaxWidth()
- .padding(horizontal = HorizontalPadding, vertical = 12.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(24.dp),
- ) {
- RadioButton(
- selected = selected,
- onClick = null,
- )
- Text(
- text = label,
- style = MaterialTheme.typography.bodyMedium,
- )
- }
- }
- private val HorizontalPadding = 24.dp
- private val VerticalPadding = 8.dp
- @ThemePreviews
- @Composable
- private fun ChapterSettingsDialogPreview() {
- TachiyomiTheme {
- Surface {
- ChapterSettingsDialogImpl(
- onDownloadFilterChanged = {},
- onUnreadFilterChanged = {},
- onBookmarkedFilterChanged = {},
- onSortModeChanged = {},
- onDisplayModeChanged = {},
- ) {}
- }
- }
- }
|