* Add Filters to Updates screen Behaves basically like the filters in the library: - Unread: Show/Don't show unread chapters - Downloaded: Show/Don't show downloaded chapters - Started: Show/Don't show chapters that have some progress but aren't fully Read - Bookmarked: Show/Don't show chapters that have been bookmarked Started behaves differently from its Library counterpart because the actual manga data is not available at this point in time and I thought calling getManga for each entry without caching would be a pretty bad idea. I have modelled this closely on the filter control flow in the Library, but I'm sure this can be simplified/adjusted in some way. * Move most filtering logic to SQL Unread, Started, and Bookmarked filters are now part of the SQL query. Download state cannot be filtered in the database so it remains in Kotlin. Because the Downloaded filter has to be run in Kotlin, the combine flow uses the preferences flow twice, once to get the SQL query params and once for the Kotlin filters (only Downloaded at this time). * Add "Hide excluded scanlators" to update filters Based on the work done in mihonapp/mihon#1623 but integrated with the other filters in this PR. Added the user as a co-author for credit. Co-authored-by: Dani <17619547+shabnix@users.noreply.github.com> --------- Co-authored-by: Dani <17619547+shabnix@users.noreply.github.com> --------- * Merge Panorama toggle into Filter diaglog * Make Panorama toggle persistent and share it with History * Implement toggleSwitch method for preference management to avoid create new function on every composition Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com> (cherry picked from commit bbe9aa8561360f030072fbc49f79748e71c6535e)
267 lines
11 KiB
Kotlin
267 lines
11 KiB
Kotlin
package eu.kanade.presentation.updates
|
|
|
|
import androidx.activity.compose.BackHandler
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.outlined.CalendarMonth
|
|
import androidx.compose.material.icons.outlined.FilterList
|
|
import androidx.compose.material.icons.outlined.FlipToBack
|
|
import androidx.compose.material.icons.outlined.Refresh
|
|
import androidx.compose.material.icons.outlined.SelectAll
|
|
import androidx.compose.material3.LocalContentColor
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.SnackbarHost
|
|
import androidx.compose.material3.SnackbarHostState
|
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
|
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.setValue
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.util.fastAll
|
|
import androidx.compose.ui.util.fastAny
|
|
import eu.kanade.presentation.components.AppBar
|
|
import eu.kanade.presentation.components.AppBarActions
|
|
import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
|
import eu.kanade.presentation.manga.components.MangaBottomActionMenu
|
|
import eu.kanade.tachiyomi.data.download.model.Download
|
|
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
|
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel
|
|
import kotlinx.collections.immutable.persistentListOf
|
|
import kotlinx.coroutines.delay
|
|
import kotlinx.coroutines.launch
|
|
import tachiyomi.domain.library.service.LibraryPreferences
|
|
import tachiyomi.i18n.MR
|
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
|
import tachiyomi.presentation.core.components.material.Scaffold
|
|
import tachiyomi.presentation.core.i18n.stringResource
|
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
|
import tachiyomi.presentation.core.theme.active
|
|
import java.time.LocalDate
|
|
import kotlin.time.Duration.Companion.seconds
|
|
|
|
@Composable
|
|
fun UpdateScreen(
|
|
state: UpdatesScreenModel.State,
|
|
snackbarHostState: SnackbarHostState,
|
|
lastUpdated: Long,
|
|
// SY -->
|
|
preserveReadingPosition: Boolean,
|
|
// SY <--
|
|
onClickCover: (UpdatesItem) -> Unit,
|
|
onSelectAll: (Boolean) -> Unit,
|
|
onInvertSelection: () -> Unit,
|
|
onCalendarClicked: () -> Unit,
|
|
onUpdateLibrary: () -> Boolean,
|
|
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
|
|
onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
|
|
onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
|
|
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
|
|
// KMK -->
|
|
updateSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
|
updateSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
|
onUpdateSwipe: (UpdatesItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
|
// KMK <--
|
|
onUpdateSelected: (UpdatesItem, /* KMK --> */ UpdatesScreenModel.UpdateSelectionOptions /* KMK <-- */) -> Unit,
|
|
onOpenChapter: (UpdatesItem) -> Unit,
|
|
onFilterClicked: () -> Unit,
|
|
hasActiveFilters: Boolean,
|
|
// KMK -->
|
|
usePanoramaCover: Boolean,
|
|
collapseToggle: (key: String) -> Unit,
|
|
// KMK <--
|
|
) {
|
|
BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
|
|
|
|
Scaffold(
|
|
topBar = { scrollBehavior ->
|
|
UpdatesAppBar(
|
|
onCalendarClicked = { onCalendarClicked() },
|
|
onUpdateLibrary = { onUpdateLibrary() },
|
|
onFilterClicked = { onFilterClicked() },
|
|
hasFilters = hasActiveFilters,
|
|
actionModeCounter = state.selected.size,
|
|
onSelectAll = { onSelectAll(true) },
|
|
onInvertSelection = { onInvertSelection() },
|
|
onCancelActionMode = { onSelectAll(false) },
|
|
scrollBehavior = scrollBehavior,
|
|
)
|
|
},
|
|
bottomBar = {
|
|
UpdatesBottomBar(
|
|
selected = state.selected,
|
|
onDownloadChapter = onDownloadChapter,
|
|
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
|
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
|
onMultiDeleteClicked = onMultiDeleteClicked,
|
|
)
|
|
},
|
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
|
) { contentPadding ->
|
|
when {
|
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
|
state.items.isEmpty() -> EmptyScreen(
|
|
stringRes = MR.strings.information_no_recent,
|
|
modifier = Modifier.padding(contentPadding),
|
|
)
|
|
else -> {
|
|
val scope = rememberCoroutineScope()
|
|
var isRefreshing by remember { mutableStateOf(false) }
|
|
|
|
PullRefresh(
|
|
refreshing = isRefreshing,
|
|
onRefresh = {
|
|
val started = onUpdateLibrary()
|
|
if (!started) return@PullRefresh
|
|
scope.launch {
|
|
// Fake refresh status but hide it after a second as it's a long running task
|
|
isRefreshing = true
|
|
delay(1.seconds)
|
|
isRefreshing = false
|
|
}
|
|
},
|
|
enabled = !state.selectionMode,
|
|
indicatorPadding = contentPadding,
|
|
) {
|
|
FastScrollLazyColumn(
|
|
contentPadding = contentPadding,
|
|
) {
|
|
updatesLastUpdatedItem(lastUpdated)
|
|
|
|
updatesUiItems(
|
|
uiModels = state.getUiModel(),
|
|
// KMK -->
|
|
expandedState = state.expandedState,
|
|
collapseToggle = collapseToggle,
|
|
usePanoramaCover = usePanoramaCover,
|
|
// KMK <--
|
|
selectionMode = state.selectionMode,
|
|
// SY -->
|
|
preserveReadingPosition = preserveReadingPosition,
|
|
// SY <--
|
|
onUpdateSelected = onUpdateSelected,
|
|
onClickCover = onClickCover,
|
|
onClickUpdate = onOpenChapter,
|
|
onDownloadChapter = onDownloadChapter,
|
|
// KMK -->
|
|
updateSwipeStartAction = updateSwipeStartAction,
|
|
updateSwipeEndAction = updateSwipeEndAction,
|
|
onUpdateSwipe = onUpdateSwipe,
|
|
// KMK <--
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun UpdatesAppBar(
|
|
onCalendarClicked: () -> Unit,
|
|
onUpdateLibrary: () -> Unit,
|
|
onFilterClicked: () -> Unit,
|
|
hasFilters: Boolean,
|
|
// For action mode
|
|
actionModeCounter: Int,
|
|
onSelectAll: () -> Unit,
|
|
onInvertSelection: () -> Unit,
|
|
onCancelActionMode: () -> Unit,
|
|
scrollBehavior: TopAppBarScrollBehavior,
|
|
modifier: Modifier = Modifier,
|
|
) {
|
|
AppBar(
|
|
modifier = modifier,
|
|
title = stringResource(MR.strings.label_recent_updates),
|
|
actions = {
|
|
AppBarActions(
|
|
persistentListOf(
|
|
AppBar.Action(
|
|
title = stringResource(MR.strings.action_filter),
|
|
icon = Icons.Outlined.FilterList,
|
|
iconTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current,
|
|
onClick = onFilterClicked,
|
|
),
|
|
AppBar.Action(
|
|
title = stringResource(MR.strings.action_view_upcoming),
|
|
icon = Icons.Outlined.CalendarMonth,
|
|
onClick = onCalendarClicked,
|
|
),
|
|
AppBar.Action(
|
|
title = stringResource(MR.strings.action_update_library),
|
|
icon = Icons.Outlined.Refresh,
|
|
onClick = onUpdateLibrary,
|
|
),
|
|
),
|
|
)
|
|
},
|
|
actionModeCounter = actionModeCounter,
|
|
onCancelActionMode = onCancelActionMode,
|
|
actionModeActions = {
|
|
AppBarActions(
|
|
persistentListOf(
|
|
AppBar.Action(
|
|
title = stringResource(MR.strings.action_select_all),
|
|
icon = Icons.Outlined.SelectAll,
|
|
onClick = onSelectAll,
|
|
),
|
|
AppBar.Action(
|
|
title = stringResource(MR.strings.action_select_inverse),
|
|
icon = Icons.Outlined.FlipToBack,
|
|
onClick = onInvertSelection,
|
|
),
|
|
),
|
|
)
|
|
},
|
|
scrollBehavior = scrollBehavior,
|
|
)
|
|
}
|
|
|
|
@Composable
|
|
private fun UpdatesBottomBar(
|
|
selected: List<UpdatesItem>,
|
|
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
|
|
onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
|
|
onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
|
|
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
|
|
) {
|
|
MangaBottomActionMenu(
|
|
visible = selected.isNotEmpty(),
|
|
modifier = Modifier.fillMaxWidth(),
|
|
onBookmarkClicked = {
|
|
onMultiBookmarkClicked.invoke(selected, true)
|
|
}.takeIf { selected.fastAny { !it.update.bookmark } },
|
|
onRemoveBookmarkClicked = {
|
|
onMultiBookmarkClicked.invoke(selected, false)
|
|
}.takeIf { selected.fastAll { it.update.bookmark } },
|
|
onMarkAsReadClicked = {
|
|
onMultiMarkAsReadClicked(selected, true)
|
|
}.takeIf { selected.fastAny { !it.update.read } },
|
|
onMarkAsUnreadClicked = {
|
|
onMultiMarkAsReadClicked(selected, false)
|
|
}.takeIf { selected.fastAny { it.update.read || it.update.lastPageRead > 0L } },
|
|
onDownloadClicked = {
|
|
onDownloadChapter(selected, ChapterDownloadAction.START)
|
|
}.takeIf {
|
|
selected.fastAny { it.downloadStateProvider() != Download.State.DOWNLOADED }
|
|
},
|
|
onDeleteClicked = {
|
|
onMultiDeleteClicked(selected)
|
|
}.takeIf { selected.fastAny { it.downloadStateProvider() == Download.State.DOWNLOADED } },
|
|
)
|
|
}
|
|
|
|
sealed interface UpdatesUiModel {
|
|
data class Header(val date: LocalDate, val mangaCount: Int) : UpdatesUiModel
|
|
open class Item(open val item: UpdatesItem, open val isExpandable: Boolean = false) : UpdatesUiModel
|
|
|
|
// KMK -->
|
|
/** The first [Item] in a group of chapters from same manga */
|
|
data class Leader(override val item: UpdatesItem, override val isExpandable: Boolean) : Item(item)
|
|
// KMK <--
|
|
}
|