feat(history): Allow multi/range-selection and batch reset for history items (#1437)
* feat(history): Allow multi-selection and batch reset for history items * Use chapterId instead of history.id * use remember getUiModel * Toolbar selection mode * cleanup History * Replace selectedChapterIds with selection for chapter selection state * Remove userSelected parameter from selection logic and related function signatures * remove parentheses * Fix range selection * longclick on cover too * disable clear-history button when none-selected
This commit is contained in:
parent
f373818d93
commit
8f57f4a5a0
10 changed files with 450 additions and 130 deletions
|
|
@ -1,16 +1,22 @@
|
||||||
package eu.kanade.presentation.history
|
package eu.kanade.presentation.history
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Checklist
|
||||||
import androidx.compose.material.icons.outlined.DeleteSweep
|
import androidx.compose.material.icons.outlined.DeleteSweep
|
||||||
import androidx.compose.material.icons.outlined.FilterList
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
|
import androidx.compose.material.icons.outlined.FlipToBack
|
||||||
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
|
@ -23,6 +29,7 @@ import eu.kanade.presentation.history.components.HistoryItem
|
||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.presentation.util.animateItemFastScroll
|
import eu.kanade.presentation.util.animateItemFastScroll
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||||
|
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel.HistorySelectionOptions
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
|
@ -45,14 +52,32 @@ fun HistoryScreen(
|
||||||
onClickFavorite: (mangaId: Long) -> Unit,
|
onClickFavorite: (mangaId: Long) -> Unit,
|
||||||
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
toggleSelectionMode: () -> Unit,
|
||||||
|
onSelectAll: (Boolean) -> Unit,
|
||||||
|
onInvertSelection: () -> Unit,
|
||||||
|
onHistorySelected: (HistoryWithRelations, HistorySelectionOptions) -> Unit,
|
||||||
onFilterClicked: () -> Unit,
|
onFilterClicked: () -> Unit,
|
||||||
hasActiveFilters: Boolean,
|
hasActiveFilters: Boolean,
|
||||||
usePanoramaCover: Boolean,
|
usePanoramaCover: Boolean,
|
||||||
// KMK <--
|
// KMK <--
|
||||||
) {
|
) {
|
||||||
|
// KMK -->
|
||||||
|
BackHandler(enabled = state.selectionMode, onBack = toggleSelectionMode)
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
SearchToolbar(
|
// KMK -->
|
||||||
|
when {
|
||||||
|
state.selectionMode -> HistorySelectionToolbar(
|
||||||
|
selectedCount = state.selection.size,
|
||||||
|
onCancelActionMode = toggleSelectionMode,
|
||||||
|
onClickSelectAll = { onSelectAll(true) },
|
||||||
|
onClickInvertSelection = onInvertSelection,
|
||||||
|
onClickClearHistory = { onDialogChange(HistoryScreenModel.Dialog.Delete(state.selected)) },
|
||||||
|
)
|
||||||
|
// KMK <--
|
||||||
|
else -> SearchToolbar(
|
||||||
titleContent = { AppBarTitle(stringResource(MR.strings.history)) },
|
titleContent = { AppBarTitle(stringResource(MR.strings.history)) },
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = state.searchQuery,
|
||||||
onChangeSearchQuery = onSearchQueryChange,
|
onChangeSearchQuery = onSearchQueryChange,
|
||||||
|
|
@ -69,21 +94,24 @@ fun HistoryScreen(
|
||||||
// KMK <--
|
// KMK <--
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(MR.strings.pref_clear_history),
|
title = stringResource(MR.strings.pref_clear_history),
|
||||||
icon = Icons.Outlined.DeleteSweep,
|
// KMK -->
|
||||||
onClick = {
|
icon = Icons.Outlined.Checklist,
|
||||||
onDialogChange(HistoryScreenModel.Dialog.DeleteAll)
|
onClick = toggleSelectionMode,
|
||||||
},
|
// KMK <--
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
state.list.let {
|
state.list.let {
|
||||||
if (it == null) {
|
// KMK -->
|
||||||
|
if (state.isLoading) {
|
||||||
|
// KMK <--
|
||||||
LoadingScreen(Modifier.padding(contentPadding))
|
LoadingScreen(Modifier.padding(contentPadding))
|
||||||
} else if (it.isEmpty()) {
|
} else if (it.isEmpty()) {
|
||||||
val msg = if (!state.searchQuery.isNullOrEmpty()) {
|
val msg = if (!state.searchQuery.isNullOrEmpty()) {
|
||||||
|
|
@ -96,14 +124,22 @@ fun HistoryScreen(
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
// KMK -->
|
||||||
|
val uiModels = remember(state.list) { state.getUiModel() }
|
||||||
|
// KMK <--
|
||||||
HistoryScreenContent(
|
HistoryScreenContent(
|
||||||
history = it,
|
// KMK -->
|
||||||
|
state = state,
|
||||||
|
history = uiModels,
|
||||||
|
// KMK <--
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onClickCover = { history -> onClickCover(history.mangaId) },
|
onClickCover = { history -> onClickCover(history.mangaId) },
|
||||||
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
||||||
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
||||||
onClickFavorite = { history -> onClickFavorite(history.mangaId) },
|
onClickFavorite = { history -> onClickFavorite(history.mangaId) },
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
selectionMode = state.selectionMode,
|
||||||
|
onHistorySelected = onHistorySelected,
|
||||||
usePanoramaCover = usePanoramaCover,
|
usePanoramaCover = usePanoramaCover,
|
||||||
// KMK <--
|
// KMK <--
|
||||||
)
|
)
|
||||||
|
|
@ -114,6 +150,9 @@ fun HistoryScreen(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun HistoryScreenContent(
|
private fun HistoryScreenContent(
|
||||||
|
// KMK -->
|
||||||
|
state: HistoryScreenModel.State,
|
||||||
|
|
||||||
history: List<HistoryUiModel>,
|
history: List<HistoryUiModel>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickCover: (HistoryWithRelations) -> Unit,
|
onClickCover: (HistoryWithRelations) -> Unit,
|
||||||
|
|
@ -121,6 +160,8 @@ private fun HistoryScreenContent(
|
||||||
onClickDelete: (HistoryWithRelations) -> Unit,
|
onClickDelete: (HistoryWithRelations) -> Unit,
|
||||||
onClickFavorite: (HistoryWithRelations) -> Unit,
|
onClickFavorite: (HistoryWithRelations) -> Unit,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
selectionMode: Boolean,
|
||||||
|
onHistorySelected: (HistoryWithRelations, HistorySelectionOptions) -> Unit,
|
||||||
usePanoramaCover: Boolean,
|
usePanoramaCover: Boolean,
|
||||||
// KMK <--
|
// KMK <--
|
||||||
) {
|
) {
|
||||||
|
|
@ -146,14 +187,40 @@ private fun HistoryScreenContent(
|
||||||
}
|
}
|
||||||
is HistoryUiModel.Item -> {
|
is HistoryUiModel.Item -> {
|
||||||
val value = item.item
|
val value = item.item
|
||||||
|
// KMK -->
|
||||||
|
val isSelected = remember(state.selection) { value.chapterId in state.selection }
|
||||||
|
// KMK <--
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
history = value,
|
history = value,
|
||||||
onClickCover = { onClickCover(value) },
|
onClickCover = { onClickCover(value) },
|
||||||
onClickResume = { onClickResume(value) },
|
// KMK -->
|
||||||
|
onClick = {
|
||||||
|
when {
|
||||||
|
selectionMode -> onHistorySelected(
|
||||||
|
item.item,
|
||||||
|
HistorySelectionOptions(
|
||||||
|
selected = !isSelected,
|
||||||
|
fromLongPress = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else -> onClickResume(value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
onHistorySelected(
|
||||||
|
item.item,
|
||||||
|
HistorySelectionOptions(
|
||||||
|
selected = !isSelected,
|
||||||
|
fromLongPress = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// KMK <--
|
||||||
onClickDelete = { onClickDelete(value) },
|
onClickDelete = { onClickDelete(value) },
|
||||||
onClickFavorite = { onClickFavorite(value) },
|
onClickFavorite = { onClickFavorite(value) },
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
selected = isSelected,
|
||||||
readProgress = value.lastPageRead
|
readProgress = value.lastPageRead
|
||||||
.takeIf { !value.read && it > 0L }
|
.takeIf { !value.read && it > 0L }
|
||||||
?.let {
|
?.let {
|
||||||
|
|
@ -174,9 +241,50 @@ private fun HistoryScreenContent(
|
||||||
|
|
||||||
sealed interface HistoryUiModel {
|
sealed interface HistoryUiModel {
|
||||||
data class Header(val date: LocalDate) : HistoryUiModel
|
data class Header(val date: LocalDate) : HistoryUiModel
|
||||||
|
// KMK -->
|
||||||
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
||||||
|
// KMK <--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
@Composable
|
||||||
|
private fun HistorySelectionToolbar(
|
||||||
|
selectedCount: Int,
|
||||||
|
onCancelActionMode: () -> Unit,
|
||||||
|
onClickSelectAll: () -> Unit,
|
||||||
|
onClickInvertSelection: () -> Unit,
|
||||||
|
onClickClearHistory: () -> Unit,
|
||||||
|
) {
|
||||||
|
AppBar(
|
||||||
|
titleContent = { Text(text = "$selectedCount") },
|
||||||
|
actions = {
|
||||||
|
AppBarActions(
|
||||||
|
persistentListOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_select_all),
|
||||||
|
icon = Icons.Outlined.SelectAll,
|
||||||
|
onClick = onClickSelectAll,
|
||||||
|
),
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_select_inverse),
|
||||||
|
icon = Icons.Outlined.FlipToBack,
|
||||||
|
onClick = onClickInvertSelection,
|
||||||
|
),
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.pref_clear_history),
|
||||||
|
icon = Icons.Outlined.DeleteSweep,
|
||||||
|
onClick = onClickClearHistory,
|
||||||
|
enabled = selectedCount > 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
isActionMode = true,
|
||||||
|
onCancelActionMode = onCancelActionMode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
@PreviewLightDark
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
internal fun HistoryScreenPreviews(
|
internal fun HistoryScreenPreviews(
|
||||||
|
|
@ -193,6 +301,10 @@ internal fun HistoryScreenPreviews(
|
||||||
onDialogChange = {},
|
onDialogChange = {},
|
||||||
onClickFavorite = {},
|
onClickFavorite = {},
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
toggleSelectionMode = {},
|
||||||
|
onSelectAll = {},
|
||||||
|
onInvertSelection = {},
|
||||||
|
onHistorySelected = { _, _ -> },
|
||||||
onFilterClicked = {},
|
onFilterClicked = {},
|
||||||
hasActiveFilters = true,
|
hasActiveFilters = true,
|
||||||
usePanoramaCover = true,
|
usePanoramaCover = true,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
import java.time.ZoneId
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
@ -17,13 +18,15 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||||
private val multiPage = HistoryScreenModel.State(
|
private val multiPage = HistoryScreenModel.State(
|
||||||
searchQuery = null,
|
searchQuery = null,
|
||||||
list =
|
list =
|
||||||
persistentListOf(HistoryUiModelExamples.headerToday)
|
// KMK -->
|
||||||
|
persistentListOf(HistoryWithRelationExamples.headerToday)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.plus(HistoryUiModelExamples.items().take(3))
|
.plus(HistoryWithRelationExamples.items().take(3))
|
||||||
.plus(HistoryUiModelExamples.header { it.minus(1, ChronoUnit.DAYS) })
|
.plus(HistoryWithRelationExamples.header { it.minus(1, ChronoUnit.DAYS) })
|
||||||
.plus(HistoryUiModelExamples.items().take(1))
|
.plus(HistoryWithRelationExamples.items().take(1))
|
||||||
.plus(HistoryUiModelExamples.header { it.minus(2, ChronoUnit.DAYS) })
|
.plus(HistoryWithRelationExamples.header { it.minus(2, ChronoUnit.DAYS) })
|
||||||
.plus(HistoryUiModelExamples.items().take(7))
|
.plus(HistoryWithRelationExamples.items().take(7))
|
||||||
|
// KMK <--
|
||||||
.toImmutableList(),
|
.toImmutableList(),
|
||||||
dialog = null,
|
dialog = null,
|
||||||
)
|
)
|
||||||
|
|
@ -31,8 +34,10 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||||
private val shortRecent = HistoryScreenModel.State(
|
private val shortRecent = HistoryScreenModel.State(
|
||||||
searchQuery = null,
|
searchQuery = null,
|
||||||
list = persistentListOf(
|
list = persistentListOf(
|
||||||
HistoryUiModelExamples.headerToday,
|
// KMK -->
|
||||||
HistoryUiModelExamples.items().first(),
|
HistoryWithRelationExamples.headerToday,
|
||||||
|
HistoryWithRelationExamples.items().first(),
|
||||||
|
// KMK <--
|
||||||
),
|
),
|
||||||
dialog = null,
|
dialog = null,
|
||||||
)
|
)
|
||||||
|
|
@ -40,8 +45,10 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||||
private val shortFuture = HistoryScreenModel.State(
|
private val shortFuture = HistoryScreenModel.State(
|
||||||
searchQuery = null,
|
searchQuery = null,
|
||||||
list = persistentListOf(
|
list = persistentListOf(
|
||||||
HistoryUiModelExamples.headerTomorrow,
|
// KMK -->
|
||||||
HistoryUiModelExamples.items().first(),
|
HistoryWithRelationExamples.headerTomorrow,
|
||||||
|
HistoryWithRelationExamples.items().first(),
|
||||||
|
// KMK <--
|
||||||
),
|
),
|
||||||
dialog = null,
|
dialog = null,
|
||||||
)
|
)
|
||||||
|
|
@ -58,7 +65,9 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||||
|
|
||||||
private val loading = HistoryScreenModel.State(
|
private val loading = HistoryScreenModel.State(
|
||||||
searchQuery = null,
|
searchQuery = null,
|
||||||
list = null,
|
// KMK -->
|
||||||
|
isLoading = true,
|
||||||
|
// KMK <--
|
||||||
dialog = null,
|
dialog = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -71,13 +80,17 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||||
loading,
|
loading,
|
||||||
)
|
)
|
||||||
|
|
||||||
private object HistoryUiModelExamples {
|
// KMK -->
|
||||||
val headerToday = header()
|
private object HistoryWithRelationExamples {
|
||||||
val headerTomorrow =
|
val headerToday = randItem()
|
||||||
HistoryUiModel.Header(LocalDate.now().plusDays(1))
|
val headerTomorrow = randItem(LocalDate.now().plusDays(1).toDate())
|
||||||
|
|
||||||
fun header(instantBuilder: (Instant) -> Instant = { it }) =
|
fun header(instantBuilder: (Instant) -> Instant = { it }) =
|
||||||
HistoryUiModel.Header(LocalDate.from(instantBuilder(Instant.now())))
|
randItem(LocalDate.from(instantBuilder(Instant.now())).toDate())
|
||||||
|
|
||||||
|
fun LocalDate.toDate(zone: ZoneId = ZoneId.systemDefault()): Date =
|
||||||
|
Date.from(atStartOfDay(zone).toInstant())
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
fun items() = sequence {
|
fun items() = sequence {
|
||||||
var count = 1
|
var count = 1
|
||||||
|
|
@ -87,8 +100,12 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun randItem(historyBuilder: (HistoryWithRelations) -> HistoryWithRelations = { it }) =
|
fun randItem(
|
||||||
HistoryUiModel.Item(
|
// KMK -->
|
||||||
|
readAt: Date = Date.from(Instant.now()),
|
||||||
|
// KMK <--
|
||||||
|
historyBuilder: (HistoryWithRelations) -> HistoryWithRelations = { it },
|
||||||
|
) =
|
||||||
historyBuilder(
|
historyBuilder(
|
||||||
HistoryWithRelations(
|
HistoryWithRelations(
|
||||||
id = Random.nextLong(),
|
id = Random.nextLong(),
|
||||||
|
|
@ -103,8 +120,8 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||||
lastPageRead = Random.nextLong(1, 10),
|
lastPageRead = Random.nextLong(1, 10),
|
||||||
totalCountCalculated = Random.nextLong(1, 100),
|
totalCountCalculated = Random.nextLong(1, 100),
|
||||||
readCountCalculated = 1,
|
readCountCalculated = 1,
|
||||||
|
readAt = readAt,
|
||||||
// KMK <--
|
// KMK <--
|
||||||
readAt = Date.from(Instant.now()),
|
|
||||||
readDuration = Random.nextLong(),
|
readDuration = Random.nextLong(),
|
||||||
coverData = MangaCover(
|
coverData = MangaCover(
|
||||||
mangaId = Random.nextLong(),
|
mangaId = Random.nextLong(),
|
||||||
|
|
@ -114,7 +131,6 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
||||||
lastModified = Random.nextLong(),
|
lastModified = Random.nextLong(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.kanade.presentation.history.components
|
package eu.kanade.presentation.history.components
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
|
@ -23,6 +23,8 @@ 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.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
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.tooling.preview.PreviewLightDark
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
|
|
@ -42,6 +44,7 @@ import tachiyomi.i18n.kmk.KMR
|
||||||
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
|
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
|
|
||||||
private val HistoryItemHeight = 96.dp
|
private val HistoryItemHeight = 96.dp
|
||||||
|
|
||||||
|
|
@ -49,11 +52,15 @@ private val HistoryItemHeight = 96.dp
|
||||||
fun HistoryItem(
|
fun HistoryItem(
|
||||||
history: HistoryWithRelations,
|
history: HistoryWithRelations,
|
||||||
onClickCover: () -> Unit,
|
onClickCover: () -> Unit,
|
||||||
onClickResume: () -> Unit,
|
// KMK -->
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
// KMK <--
|
||||||
onClickDelete: () -> Unit,
|
onClickDelete: () -> Unit,
|
||||||
onClickFavorite: () -> Unit,
|
onClickFavorite: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
selected: Boolean,
|
||||||
readProgress: String?,
|
readProgress: String?,
|
||||||
hasUnread: Boolean,
|
hasUnread: Boolean,
|
||||||
usePanoramaCover: Boolean,
|
usePanoramaCover: Boolean,
|
||||||
|
|
@ -61,11 +68,21 @@ fun HistoryItem(
|
||||||
// KMK <--
|
// KMK <--
|
||||||
) {
|
) {
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
val textAlpha = if (history.read) DISABLED_ALPHA else 1f
|
val textAlpha = if (history.read) DISABLED_ALPHA else 1f
|
||||||
// KMK <--
|
// KMK <--
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clickable(onClick = onClickResume)
|
// KMK -->
|
||||||
|
.selectedBackground(selected)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onLongClick()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// KMK <--
|
||||||
.height(HistoryItemHeight)
|
.height(HistoryItemHeight)
|
||||||
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
|
@ -78,16 +95,24 @@ fun HistoryItem(
|
||||||
if (DebugToggles.HIDE_COVER_IMAGE_ONLY_SHOW_COLOR.enabled) {
|
if (DebugToggles.HIDE_COVER_IMAGE_ONLY_SHOW_COLOR.enabled) {
|
||||||
MangaCoverHide.Book(
|
MangaCoverHide.Book(
|
||||||
modifier = Modifier.fillMaxHeight(),
|
modifier = Modifier.fillMaxHeight(),
|
||||||
bgColor = bgColor,
|
bgColor = bgColor ?: MaterialTheme.colorScheme.surface.takeIf { selected },
|
||||||
tint = onBgColor,
|
tint = onBgColor,
|
||||||
size = MangaCover.Size.Medium,
|
size = MangaCover.Size.Medium,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if (usePanoramaCover && coverIsWide) {
|
if (usePanoramaCover && coverIsWide) {
|
||||||
MangaCover.Panorama(
|
MangaCover.Panorama(
|
||||||
modifier = Modifier.fillMaxHeight(),
|
modifier = Modifier.fillMaxHeight()
|
||||||
data = mangaCover,
|
// KMK -->
|
||||||
|
.combinedClickable(
|
||||||
onClick = onClickCover,
|
onClick = onClickCover,
|
||||||
|
onLongClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onLongClick()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// KMK <--
|
||||||
|
data = mangaCover,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
bgColor = bgColor,
|
bgColor = bgColor,
|
||||||
tint = onBgColor,
|
tint = onBgColor,
|
||||||
|
|
@ -101,9 +126,17 @@ fun HistoryItem(
|
||||||
} else {
|
} else {
|
||||||
// KMK <--
|
// KMK <--
|
||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
modifier = Modifier.fillMaxHeight(),
|
modifier = Modifier.fillMaxHeight()
|
||||||
data = mangaCover,
|
// KMK -->
|
||||||
|
.combinedClickable(
|
||||||
onClick = onClickCover,
|
onClick = onClickCover,
|
||||||
|
onLongClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onLongClick()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// KMK <--
|
||||||
|
data = mangaCover,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
bgColor = bgColor,
|
bgColor = bgColor,
|
||||||
tint = onBgColor,
|
tint = onBgColor,
|
||||||
|
|
@ -209,12 +242,18 @@ private fun HistoryItemPreviews(
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
history = historyWithRelations,
|
history = historyWithRelations,
|
||||||
onClickCover = {},
|
onClickCover = {},
|
||||||
onClickResume = {},
|
// KMK -->
|
||||||
|
onClick = {},
|
||||||
|
onLongClick = {},
|
||||||
|
// KMK <--
|
||||||
onClickDelete = {},
|
onClickDelete = {},
|
||||||
onClickFavorite = {},
|
onClickFavorite = {},
|
||||||
readProgress = "Page 5",
|
readProgress = "Page 5",
|
||||||
|
// KMK -->
|
||||||
hasUnread = true,
|
hasUnread = true,
|
||||||
|
selected = true,
|
||||||
usePanoramaCover = false,
|
usePanoramaCover = false,
|
||||||
|
// KMK <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.history
|
||||||
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.util.fastFilter
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.core.util.insertSeparators
|
import eu.kanade.core.util.insertSeparators
|
||||||
|
|
@ -10,6 +11,7 @@ import eu.kanade.domain.track.interactor.AddTracks
|
||||||
import eu.kanade.presentation.history.HistoryUiModel
|
import eu.kanade.presentation.history.HistoryUiModel
|
||||||
import eu.kanade.tachiyomi.util.lang.toLocalDate
|
import eu.kanade.tachiyomi.util.lang.toLocalDate
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
|
@ -26,6 +28,7 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.common.utils.mutate
|
||||||
import tachiyomi.core.common.preference.CheckboxState
|
import tachiyomi.core.common.preference.CheckboxState
|
||||||
import tachiyomi.core.common.preference.TriState
|
import tachiyomi.core.common.preference.TriState
|
||||||
import tachiyomi.core.common.preference.mapAsCheckboxState
|
import tachiyomi.core.common.preference.mapAsCheckboxState
|
||||||
|
|
@ -71,6 +74,11 @@ class HistoryScreenModel(
|
||||||
private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
|
private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
|
||||||
val events: Flow<Event> = _events.receiveAsFlow()
|
val events: Flow<Event> = _events.receiveAsFlow()
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
// First and last selected index in list
|
||||||
|
private val selectedPositions: Array<Int> = arrayOf(-1, -1)
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
screenModelScope.launch {
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
|
@ -97,10 +105,18 @@ class HistoryScreenModel(
|
||||||
logcat(LogPriority.ERROR, error)
|
logcat(LogPriority.ERROR, error)
|
||||||
_events.send(Event.InternalError)
|
_events.send(Event.InternalError)
|
||||||
}
|
}
|
||||||
.map { it.toHistoryUiModels() }
|
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
.collect { newList -> mutableState.update { it.copy(list = newList) } }
|
.collect { newList ->
|
||||||
|
mutableState.update {
|
||||||
|
it.copy(
|
||||||
|
// KMK -->
|
||||||
|
isLoading = false,
|
||||||
|
list = newList.toImmutableList(),
|
||||||
|
// KMK <--
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
|
@ -123,19 +139,6 @@ class HistoryScreenModel(
|
||||||
// KMK <--
|
// KMK <--
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<HistoryWithRelations>.toHistoryUiModels(): List<HistoryUiModel> {
|
|
||||||
return map { HistoryUiModel.Item(it) }
|
|
||||||
.insertSeparators { before, after ->
|
|
||||||
val beforeDate = before?.item?.readAt?.time?.toLocalDate()
|
|
||||||
val afterDate = after?.item?.readAt?.time?.toLocalDate()
|
|
||||||
when {
|
|
||||||
beforeDate != afterDate && afterDate != null -> HistoryUiModel.Header(afterDate)
|
|
||||||
// Return null to avoid adding a separator between two items.
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getNextChapter(): Chapter? {
|
suspend fun getNextChapter(): Chapter? {
|
||||||
return withIOContext { getNextChapters.await(onlyUnread = false).firstOrNull() }
|
return withIOContext { getNextChapters.await(onlyUnread = false).firstOrNull() }
|
||||||
}
|
}
|
||||||
|
|
@ -151,17 +154,21 @@ class HistoryScreenModel(
|
||||||
_events.send(Event.OpenChapter(chapter))
|
_events.send(Event.OpenChapter(chapter))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeFromHistory(history: HistoryWithRelations) {
|
// KMK -->
|
||||||
|
fun removeFromHistory(toDelete: List<HistoryWithRelations>) {
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
removeHistory.await(history)
|
removeHistory.await(toDelete.map { it.id })
|
||||||
}
|
}
|
||||||
|
toggleSelectionMode(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAllFromHistory(mangaId: Long) {
|
fun removeAllFromHistory(toDelete: List<HistoryWithRelations>) {
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
removeHistory.await(mangaId)
|
removeHistory.awaitManga(toDelete.map { it.mangaId })
|
||||||
}
|
}
|
||||||
|
toggleSelectionMode(false)
|
||||||
}
|
}
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
fun removeAllHistory() {
|
fun removeAllHistory() {
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
|
|
@ -169,6 +176,7 @@ class HistoryScreenModel(
|
||||||
if (!result) return@launchIO
|
if (!result) return@launchIO
|
||||||
_events.send(Event.HistoryCleared)
|
_events.send(Event.HistoryCleared)
|
||||||
}
|
}
|
||||||
|
toggleSelectionMode(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSearchQuery(query: String?) {
|
fun updateSearchQuery(query: String?) {
|
||||||
|
|
@ -280,6 +288,113 @@ class HistoryScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
data class HistorySelectionOptions(
|
||||||
|
val selected: Boolean,
|
||||||
|
val fromLongPress: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun toggleSelection(
|
||||||
|
item: HistoryWithRelations,
|
||||||
|
selectionOptions: HistorySelectionOptions,
|
||||||
|
) {
|
||||||
|
val (selected, fromLongPress) = selectionOptions
|
||||||
|
if (item.chapterId in state.value.selection == selected) return
|
||||||
|
|
||||||
|
mutableState.update { state ->
|
||||||
|
val selection = state.selection.mutate { list ->
|
||||||
|
state.list.run {
|
||||||
|
val selectedIndex = indexOfFirst { it.chapterId == item.chapterId }
|
||||||
|
if (selectedIndex < 0) return@run
|
||||||
|
|
||||||
|
val firstSelection = list.isEmpty()
|
||||||
|
if (selected) list.add(item.chapterId) else list.remove(item.chapterId)
|
||||||
|
|
||||||
|
if (firstSelection) {
|
||||||
|
// Since it can go into selectionMode from toolbar (without long press), we need to set both positions here
|
||||||
|
selectedPositions[0] = selectedIndex
|
||||||
|
selectedPositions[1] = selectedIndex
|
||||||
|
} else if (selected && fromLongPress) {
|
||||||
|
// Try to select the items in-between when possible
|
||||||
|
val range: IntRange
|
||||||
|
if (selectedIndex < selectedPositions[0]) {
|
||||||
|
range = selectedIndex + 1..<selectedPositions[0]
|
||||||
|
selectedPositions[0] = selectedIndex
|
||||||
|
} else if (selectedIndex > selectedPositions[1]) {
|
||||||
|
range = (selectedPositions[1] + 1)..<selectedIndex
|
||||||
|
selectedPositions[1] = selectedIndex
|
||||||
|
} else {
|
||||||
|
// Just select itself
|
||||||
|
range = IntRange.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
range.forEach {
|
||||||
|
val inBetweenItem = get(it)
|
||||||
|
if (inBetweenItem.chapterId !in list) {
|
||||||
|
list.add(inBetweenItem.chapterId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!fromLongPress) {
|
||||||
|
if (!selected) {
|
||||||
|
if (selectedIndex == selectedPositions[0]) {
|
||||||
|
selectedPositions[0] = indexOfFirst { it.chapterId in list }
|
||||||
|
} else if (selectedIndex == selectedPositions[1]) {
|
||||||
|
selectedPositions[1] = indexOfLast { it.chapterId in list }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedIndex < selectedPositions[0]) {
|
||||||
|
selectedPositions[0] = selectedIndex
|
||||||
|
} else if (selectedIndex > selectedPositions[1]) {
|
||||||
|
selectedPositions[1] = selectedIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.copy(
|
||||||
|
selection = selection,
|
||||||
|
selectionMode = selected || selection.isNotEmpty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleAllSelection(selected: Boolean) {
|
||||||
|
mutableState.update { state ->
|
||||||
|
val selection = if (selected) {
|
||||||
|
state.list.mapTo(mutableSetOf()) { it.chapterId }
|
||||||
|
} else {
|
||||||
|
emptySet()
|
||||||
|
}
|
||||||
|
selectedPositions[0] = -1
|
||||||
|
selectedPositions[1] = -1
|
||||||
|
state.copy(
|
||||||
|
selection = selection,
|
||||||
|
selectionMode = selected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invertSelection() {
|
||||||
|
mutableState.update { state ->
|
||||||
|
val selection = state.selection.mutate { list ->
|
||||||
|
state.list.forEach { item ->
|
||||||
|
if (!list.remove(item.chapterId)) list.add(item.chapterId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedPositions[0] = -1
|
||||||
|
selectedPositions[1] = -1
|
||||||
|
state.copy(selection = selection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleSelectionMode(newMode: Boolean? = null) {
|
||||||
|
if (newMode == false || state.value.selectionMode) {
|
||||||
|
toggleAllSelection(false)
|
||||||
|
} else {
|
||||||
|
mutableState.update { it.copy(selectionMode = newMode ?: !it.selectionMode) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
private fun getHistoryItemPreferenceFlow(): Flow<ItemPreferences> {
|
private fun getHistoryItemPreferenceFlow(): Flow<ItemPreferences> {
|
||||||
return combine(
|
return combine(
|
||||||
historyPreferences.filterUnfinishedManga().changes(),
|
historyPreferences.filterUnfinishedManga().changes(),
|
||||||
|
|
@ -309,16 +424,39 @@ class HistoryScreenModel(
|
||||||
@Immutable
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val searchQuery: String? = null,
|
val searchQuery: String? = null,
|
||||||
val list: List<HistoryUiModel>? = null,
|
// KMK -->
|
||||||
|
val list: ImmutableList<HistoryWithRelations> = persistentListOf(),
|
||||||
|
val isLoading: Boolean = true,
|
||||||
|
// KMK <--
|
||||||
val dialog: Dialog? = null,
|
val dialog: Dialog? = null,
|
||||||
// KMk -->
|
// KMk -->
|
||||||
|
val selection: Set<Long> = emptySet(),
|
||||||
val hasActiveFilters: Boolean = false,
|
val hasActiveFilters: Boolean = false,
|
||||||
|
val selectionMode: Boolean = false,
|
||||||
|
) {
|
||||||
|
val selected
|
||||||
|
get() = list.fastFilter { it.chapterId in selection }
|
||||||
|
|
||||||
|
fun getUiModel() = list.map { HistoryUiModel.Item(it) }
|
||||||
|
.insertSeparators { before, after ->
|
||||||
|
val beforeDate = before?.item?.readAt?.time?.toLocalDate()
|
||||||
|
val afterDate = after?.item?.readAt?.time?.toLocalDate()
|
||||||
|
when {
|
||||||
|
beforeDate != afterDate && afterDate != null -> HistoryUiModel.Header(afterDate)
|
||||||
|
// Return null to avoid adding a separator between two items.
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// KMK <--
|
// KMK <--
|
||||||
)
|
|
||||||
|
|
||||||
sealed interface Dialog {
|
sealed interface Dialog {
|
||||||
data object DeleteAll : Dialog
|
data object DeleteAll : Dialog
|
||||||
data class Delete(val history: HistoryWithRelations) : Dialog
|
// KMK -->
|
||||||
|
data class Delete(val histories: List<HistoryWithRelations>) : Dialog {
|
||||||
|
constructor(history: HistoryWithRelations) : this(listOf(history))
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
data class DuplicateManga(val manga: Manga, val duplicates: List<MangaWithChapterCount>) : Dialog
|
data class DuplicateManga(val manga: Manga, val duplicates: List<MangaWithChapterCount>) : Dialog
|
||||||
data class ChangeCategory(
|
data class ChangeCategory(
|
||||||
val manga: Manga,
|
val manga: Manga,
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,10 @@ data object HistoryTab : Tab {
|
||||||
onDialogChange = screenModel::setDialog,
|
onDialogChange = screenModel::setDialog,
|
||||||
onClickFavorite = screenModel::addFavorite,
|
onClickFavorite = screenModel::addFavorite,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
toggleSelectionMode = screenModel::toggleSelectionMode,
|
||||||
|
onSelectAll = screenModel::toggleAllSelection,
|
||||||
|
onInvertSelection = screenModel::invertSelection,
|
||||||
|
onHistorySelected = screenModel::toggleSelection,
|
||||||
onFilterClicked = screenModel::showFilterDialog,
|
onFilterClicked = screenModel::showFilterDialog,
|
||||||
hasActiveFilters = state.hasActiveFilters,
|
hasActiveFilters = state.hasActiveFilters,
|
||||||
usePanoramaCover = usePanoramaCover,
|
usePanoramaCover = usePanoramaCover,
|
||||||
|
|
@ -113,11 +117,13 @@ data object HistoryTab : Tab {
|
||||||
HistoryDeleteDialog(
|
HistoryDeleteDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onDelete = { all ->
|
onDelete = { all ->
|
||||||
|
// KMK -->
|
||||||
if (all) {
|
if (all) {
|
||||||
screenModel.removeAllFromHistory(dialog.history.mangaId)
|
screenModel.removeAllFromHistory(dialog.histories)
|
||||||
} else {
|
} else {
|
||||||
screenModel.removeFromHistory(dialog.history)
|
screenModel.removeFromHistory(dialog.histories)
|
||||||
}
|
}
|
||||||
|
// KMK <--
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -169,8 +175,10 @@ data object HistoryTab : Tab {
|
||||||
null -> {}
|
null -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.list) {
|
// KMK -->
|
||||||
if (state.list != null) {
|
LaunchedEffect(state.isLoading) {
|
||||||
|
if (!state.isLoading) {
|
||||||
|
// KMK <--
|
||||||
(context as? MainActivity)?.ready = true
|
(context as? MainActivity)?.ready = true
|
||||||
|
|
||||||
// AM (DISCORD) -->
|
// AM (DISCORD) -->
|
||||||
|
|
|
||||||
|
|
@ -125,9 +125,9 @@ class EHentaiUpdateHelper(context: Context) {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Delete the duplicate history first
|
// Delete the duplicate history first
|
||||||
deleteHistory.forEach {
|
// KMK -->
|
||||||
removeHistory.awaitById(it)
|
removeHistory.await(deleteHistory)
|
||||||
}
|
// KMK <--
|
||||||
|
|
||||||
// Insert new history
|
// Insert new history
|
||||||
newHistory.forEach {
|
newHistory.forEach {
|
||||||
|
|
|
||||||
|
|
@ -56,17 +56,21 @@ class HistoryRepositoryImpl(
|
||||||
return handler.awaitList { historyQueries.getHistoryByMangaId(mangaId, HistoryMapper::mapHistory) }
|
return handler.awaitList { historyQueries.getHistoryByMangaId(mangaId, HistoryMapper::mapHistory) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun resetHistory(historyId: Long) {
|
// KMK -->
|
||||||
|
override suspend fun resetHistory(historyIds: List<Long>) {
|
||||||
try {
|
try {
|
||||||
handler.await { historyQueries.resetHistoryById(historyId) }
|
handler.await { historyQueries.resetHistoryByIds(historyIds) }
|
||||||
|
// KMK <--
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, throwable = e)
|
logcat(LogPriority.ERROR, throwable = e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun resetHistoryByMangaId(mangaId: Long) {
|
// KMK -->
|
||||||
|
override suspend fun resetHistoryByMangaIds(mangaIds: List<Long>) {
|
||||||
try {
|
try {
|
||||||
handler.await { historyQueries.resetHistoryByMangaId(mangaId) }
|
handler.await { historyQueries.resetHistoryByMangaIds(mangaIds) }
|
||||||
|
// KMK <--
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, throwable = e)
|
logcat(LogPriority.ERROR, throwable = e)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,16 @@ JOIN chapters C
|
||||||
ON H.chapter_id = C._id
|
ON H.chapter_id = C._id
|
||||||
WHERE C.manga_id = :mangaId AND C.url = :chapterUrl AND C._id = H.chapter_id;
|
WHERE C.manga_id = :mangaId AND C.url = :chapterUrl AND C._id = H.chapter_id;
|
||||||
|
|
||||||
resetHistoryById:
|
-- KMK -->
|
||||||
|
resetHistoryByIds:
|
||||||
|
-- KMK <--
|
||||||
UPDATE history
|
UPDATE history
|
||||||
SET last_read = 0
|
SET last_read = 0
|
||||||
WHERE _id = :historyId;
|
-- KMK -->
|
||||||
|
WHERE _id IN :historyIds;
|
||||||
|
|
||||||
resetHistoryByMangaId:
|
resetHistoryByMangaIds:
|
||||||
|
-- KMK <--
|
||||||
UPDATE history
|
UPDATE history
|
||||||
SET last_read = 0
|
SET last_read = 0
|
||||||
WHERE _id IN (
|
WHERE _id IN (
|
||||||
|
|
@ -48,7 +52,9 @@ WHERE _id IN (
|
||||||
ON M._id = C.manga_id
|
ON M._id = C.manga_id
|
||||||
INNER JOIN history H
|
INNER JOIN history H
|
||||||
ON C._id = H.chapter_id
|
ON C._id = H.chapter_id
|
||||||
WHERE M._id = :mangaId
|
-- KMK -->
|
||||||
|
WHERE M._id IN :mangaIds
|
||||||
|
-- KMK <--
|
||||||
);
|
);
|
||||||
|
|
||||||
removeAllHistory:
|
removeAllHistory:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package tachiyomi.domain.history.interactor
|
package tachiyomi.domain.history.interactor
|
||||||
|
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
|
||||||
import tachiyomi.domain.history.repository.HistoryRepository
|
import tachiyomi.domain.history.repository.HistoryRepository
|
||||||
|
|
||||||
class RemoveHistory(
|
class RemoveHistory(
|
||||||
|
|
@ -11,17 +10,13 @@ class RemoveHistory(
|
||||||
return repository.deleteAllHistory()
|
return repository.deleteAllHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun await(history: HistoryWithRelations) {
|
// KMK -->
|
||||||
repository.resetHistory(history.id)
|
suspend fun await(historyIds: List<Long>) {
|
||||||
|
repository.resetHistory(historyIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun await(mangaId: Long) {
|
suspend fun awaitManga(mangaIds: List<Long>) {
|
||||||
repository.resetHistoryByMangaId(mangaId)
|
repository.resetHistoryByMangaIds(mangaIds)
|
||||||
}
|
}
|
||||||
|
// KMK <--
|
||||||
// SY -->
|
|
||||||
suspend fun awaitById(historyId: Long) {
|
|
||||||
repository.resetHistory(historyId)
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,11 @@ interface HistoryRepository {
|
||||||
|
|
||||||
suspend fun getHistoryByMangaId(mangaId: Long): List<History>
|
suspend fun getHistoryByMangaId(mangaId: Long): List<History>
|
||||||
|
|
||||||
suspend fun resetHistory(historyId: Long)
|
// KMK -->
|
||||||
|
suspend fun resetHistory(historyIds: List<Long>)
|
||||||
|
|
||||||
suspend fun resetHistoryByMangaId(mangaId: Long)
|
suspend fun resetHistoryByMangaIds(mangaIds: List<Long>)
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
suspend fun deleteAllHistory(): Boolean
|
suspend fun deleteAllHistory(): Boolean
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue