feat(update-errors): Allow manually delete update errors (#916)
* Allow manually delete error(s) * remember swipe action & correct Modifier * Simplify LibraryUpdateError data structure and separate SourceManager from compose UI * Add badge count errors * using SwipeToDismiss * correct Modifier & StringResource * remove confirmValueChange when Settle
This commit is contained in:
parent
bc33a75b6b
commit
2024cdf168
13 changed files with 225 additions and 84 deletions
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.shape.ZeroCornerSize
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.ArrowDownward
|
import androidx.compose.material.icons.outlined.ArrowDownward
|
||||||
import androidx.compose.material.icons.outlined.ArrowUpward
|
import androidx.compose.material.icons.outlined.ArrowUpward
|
||||||
|
import androidx.compose.material.icons.outlined.DeleteOutline
|
||||||
import androidx.compose.material.icons.outlined.FindReplace
|
import androidx.compose.material.icons.outlined.FindReplace
|
||||||
import androidx.compose.material.icons.outlined.FlipToBack
|
import androidx.compose.material.icons.outlined.FlipToBack
|
||||||
import androidx.compose.material.icons.outlined.SelectAll
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
|
|
@ -63,6 +64,8 @@ fun LibraryUpdateErrorScreen(
|
||||||
onMultiMigrateClicked: (() -> Unit),
|
onMultiMigrateClicked: (() -> Unit),
|
||||||
onSelectAll: (Boolean) -> Unit,
|
onSelectAll: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
|
onErrorsDelete: () -> Unit,
|
||||||
|
onErrorDelete: (Long) -> Unit,
|
||||||
onErrorSelected: (LibraryUpdateErrorItem, Boolean, Boolean, Boolean) -> Unit,
|
onErrorSelected: (LibraryUpdateErrorItem, Boolean, Boolean, Boolean) -> Unit,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
@ -112,6 +115,7 @@ fun LibraryUpdateErrorScreen(
|
||||||
onClickUnselectAll = { onSelectAll(false) },
|
onClickUnselectAll = { onSelectAll(false) },
|
||||||
onClickSelectAll = { onSelectAll(true) },
|
onClickSelectAll = { onSelectAll(true) },
|
||||||
onClickInvertSelection = onInvertSelection,
|
onClickInvertSelection = onInvertSelection,
|
||||||
|
onClickDeleteErrors = onErrorsDelete,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -173,6 +177,7 @@ fun LibraryUpdateErrorScreen(
|
||||||
onErrorSelected = onErrorSelected,
|
onErrorSelected = onErrorSelected,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onClickCover = onClickCover,
|
onClickCover = onClickCover,
|
||||||
|
onDelete = onErrorDelete,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -303,6 +308,7 @@ private fun LibraryUpdateErrorAppBar(
|
||||||
onClickUnselectAll: () -> Unit,
|
onClickUnselectAll: () -> Unit,
|
||||||
onClickSelectAll: () -> Unit,
|
onClickSelectAll: () -> Unit,
|
||||||
onClickInvertSelection: () -> Unit,
|
onClickInvertSelection: () -> Unit,
|
||||||
|
onClickDeleteErrors: () -> Unit,
|
||||||
scrollBehavior: TopAppBarScrollBehavior,
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
) {
|
) {
|
||||||
AppBar(
|
AppBar(
|
||||||
|
|
@ -326,6 +332,11 @@ private fun LibraryUpdateErrorAppBar(
|
||||||
actionModeActions = {
|
actionModeActions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_delete),
|
||||||
|
icon = Icons.Outlined.DeleteOutline,
|
||||||
|
onClick = onClickDeleteErrors,
|
||||||
|
),
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(MR.strings.action_select_all),
|
title = stringResource(MR.strings.action_select_all),
|
||||||
icon = Icons.Outlined.SelectAll,
|
icon = Icons.Outlined.SelectAll,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,28 @@
|
||||||
package eu.kanade.presentation.libraryUpdateError.components
|
package eu.kanade.presentation.libraryUpdateError.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SwipeToDismissBox
|
||||||
|
import androidx.compose.material3.SwipeToDismissBoxState
|
||||||
|
import androidx.compose.material3.SwipeToDismissBoxValue.EndToStart
|
||||||
|
import androidx.compose.material3.SwipeToDismissBoxValue.Settled
|
||||||
|
import androidx.compose.material3.SwipeToDismissBoxValue.StartToEnd
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
|
@ -19,14 +33,13 @@ import eu.kanade.presentation.manga.components.MangaCover
|
||||||
import eu.kanade.presentation.util.animateItemFastScroll
|
import eu.kanade.presentation.util.animateItemFastScroll
|
||||||
import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorItem
|
import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorItem
|
||||||
import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations
|
import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.ListGroupHeader
|
import tachiyomi.presentation.core.components.ListGroupHeader
|
||||||
import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
internal fun LazyListScope.libraryUpdateErrorUiItems(
|
internal fun LazyListScope.libraryUpdateErrorUiItems(
|
||||||
uiModels: List<LibraryUpdateErrorUiModel>,
|
uiModels: List<LibraryUpdateErrorUiModel>,
|
||||||
|
|
@ -34,6 +47,7 @@ internal fun LazyListScope.libraryUpdateErrorUiItems(
|
||||||
onErrorSelected: (LibraryUpdateErrorItem, Boolean, Boolean, Boolean) -> Unit,
|
onErrorSelected: (LibraryUpdateErrorItem, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onClick: (LibraryUpdateErrorItem) -> Unit,
|
onClick: (LibraryUpdateErrorItem) -> Unit,
|
||||||
onClickCover: (LibraryUpdateErrorItem) -> Unit,
|
onClickCover: (LibraryUpdateErrorItem) -> Unit,
|
||||||
|
onDelete: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
uiModels.forEach { uiModel ->
|
uiModels.forEach { uiModel ->
|
||||||
when (uiModel) {
|
when (uiModel) {
|
||||||
|
|
@ -46,6 +60,7 @@ internal fun LazyListScope.libraryUpdateErrorUiItems(
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
text = uiModel.errorMessage,
|
text = uiModel.errorMessage,
|
||||||
tonalElevation = 1.dp,
|
tonalElevation = 1.dp,
|
||||||
|
count = uiModel.count,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +73,8 @@ internal fun LazyListScope.libraryUpdateErrorUiItems(
|
||||||
LibraryUpdateErrorUiItem(
|
LibraryUpdateErrorUiItem(
|
||||||
modifier = Modifier.animateItemFastScroll(),
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
error = libraryUpdateErrorItem.error,
|
error = libraryUpdateErrorItem.error,
|
||||||
|
mangaCover = libraryUpdateErrorItem.mangaCover,
|
||||||
|
sourceName = libraryUpdateErrorItem.sourceName,
|
||||||
selected = libraryUpdateErrorItem.selected,
|
selected = libraryUpdateErrorItem.selected,
|
||||||
onClick = {
|
onClick = {
|
||||||
when {
|
when {
|
||||||
|
|
@ -80,6 +97,7 @@ internal fun LazyListScope.libraryUpdateErrorUiItems(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClickCover = { onClickCover(libraryUpdateErrorItem) }.takeIf { !selectionMode },
|
onClickCover = { onClickCover(libraryUpdateErrorItem) }.takeIf { !selectionMode },
|
||||||
|
onSwipe = { onDelete(libraryUpdateErrorItem.error.errorId) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,15 +109,37 @@ internal fun LazyListScope.libraryUpdateErrorUiItems(
|
||||||
private fun LibraryUpdateErrorUiItem(
|
private fun LibraryUpdateErrorUiItem(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
error: LibraryUpdateErrorWithRelations,
|
error: LibraryUpdateErrorWithRelations,
|
||||||
|
mangaCover: tachiyomi.domain.manga.model.MangaCover,
|
||||||
|
sourceName: String,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClickCover: (() -> Unit)?,
|
onClickCover: (() -> Unit)?,
|
||||||
|
onSwipe: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
|
val dismissState = rememberSwipeToDismissBoxState(
|
||||||
|
confirmValueChange = {
|
||||||
|
when (it) {
|
||||||
|
StartToEnd -> onSwipe()
|
||||||
|
EndToStart -> onSwipe()
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
return@rememberSwipeToDismissBoxState true
|
||||||
|
},
|
||||||
|
// Set threshold to 25% of the width
|
||||||
|
positionalThreshold = { totalDistance -> totalDistance * 0.25f },
|
||||||
|
)
|
||||||
|
|
||||||
|
SwipeToDismissBox(
|
||||||
|
state = dismissState,
|
||||||
|
modifier = modifier,
|
||||||
|
backgroundContent = { DismissBackground(dismissState) },
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
.selectedBackground(selected)
|
.selectedBackground(selected)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
|
@ -115,7 +155,7 @@ private fun LibraryUpdateErrorUiItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 6.dp)
|
.padding(vertical = 6.dp)
|
||||||
.height(48.dp),
|
.height(48.dp),
|
||||||
data = error.mangaCover,
|
data = mangaCover,
|
||||||
onClick = onClickCover,
|
onClick = onClickCover,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -132,7 +172,7 @@ private fun LibraryUpdateErrorUiItem(
|
||||||
|
|
||||||
Row(modifier = Modifier.padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(modifier = Modifier.padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(
|
Text(
|
||||||
text = Injekt.get<SourceManager>().getOrStub(error.mangaSource).name,
|
text = sourceName,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
overflow = TextOverflow.Visible,
|
overflow = TextOverflow.Visible,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
|
@ -143,11 +183,50 @@ private fun LibraryUpdateErrorUiItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DismissBackground(dismissState: SwipeToDismissBoxState) {
|
||||||
|
val direction = dismissState.dismissDirection
|
||||||
|
val targetState = dismissState.targetValue
|
||||||
|
|
||||||
|
val backgroundColor by animateColorAsState(
|
||||||
|
when (direction) {
|
||||||
|
Settled ->
|
||||||
|
MaterialTheme.colorScheme.surface
|
||||||
|
StartToEnd ->
|
||||||
|
MaterialTheme.colorScheme.errorContainer
|
||||||
|
.copy(alpha = if (targetState == Settled) 0.45f else 1f)
|
||||||
|
EndToStart ->
|
||||||
|
MaterialTheme.colorScheme.errorContainer
|
||||||
|
.copy(alpha = if (targetState == Settled) 0.45f else 1f)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val alignment = when (direction) {
|
||||||
|
StartToEnd -> Alignment.CenterStart
|
||||||
|
EndToStart -> Alignment.CenterEnd
|
||||||
|
else -> Alignment.Center
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(backgroundColor)
|
||||||
|
.padding(horizontal = 20.dp),
|
||||||
|
contentAlignment = alignment,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Delete,
|
||||||
|
contentDescription = stringResource(MR.strings.action_delete),
|
||||||
|
tint = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class LibraryUpdateErrorUiModel {
|
sealed class LibraryUpdateErrorUiModel {
|
||||||
|
|
||||||
data class Header(val errorMessage: String) : LibraryUpdateErrorUiModel()
|
data class Header(val errorMessage: String, val count: Int) : LibraryUpdateErrorUiModel()
|
||||||
|
|
||||||
data class Item(val item: LibraryUpdateErrorItem) : LibraryUpdateErrorUiModel()
|
data class Item(val item: LibraryUpdateErrorItem) : LibraryUpdateErrorUiModel()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,14 +96,14 @@ fun MangaChapterListItem(
|
||||||
}
|
}
|
||||||
|
|
||||||
SwipeableActionsBox(
|
SwipeableActionsBox(
|
||||||
modifier = Modifier.clipToBounds(),
|
modifier = modifier.clipToBounds(),
|
||||||
startActions = listOfNotNull(swipeStart),
|
startActions = listOfNotNull(swipeStart),
|
||||||
endActions = listOfNotNull(swipeEnd),
|
endActions = listOfNotNull(swipeEnd),
|
||||||
swipeThreshold = swipeActionThreshold,
|
swipeThreshold = swipeActionThreshold,
|
||||||
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
|
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = Modifier
|
||||||
.selectedBackground(selected)
|
.selectedBackground(selected)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
|
@ -244,7 +244,7 @@ internal fun getSwipeAction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun swipeAction(
|
internal fun swipeAction(
|
||||||
onSwipe: () -> Unit,
|
onSwipe: () -> Unit,
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
background: Color,
|
background: Color,
|
||||||
|
|
|
||||||
|
|
@ -655,7 +655,7 @@ class LibraryUpdateJob(private val context: Context, private val workerParams: W
|
||||||
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
private suspend fun clearErrorFromDB(mangaId: Long) {
|
private suspend fun clearErrorFromDB(mangaId: Long) {
|
||||||
deleteLibraryUpdateErrors.deleteMangaError(mangaId = mangaId)
|
deleteLibraryUpdateErrors.deleteMangaError(mangaIds = listOf(mangaId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun writeErrorToDB(error: Pair<Manga, String?>) {
|
private suspend fun writeErrorToDB(error: Pair<Manga, String?>) {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ class LibraryUpdateErrorScreen : Screen() {
|
||||||
},
|
},
|
||||||
onSelectAll = screenModel::toggleAllSelection,
|
onSelectAll = screenModel::toggleAllSelection,
|
||||||
onInvertSelection = screenModel::invertSelection,
|
onInvertSelection = screenModel::invertSelection,
|
||||||
|
onErrorsDelete = screenModel::deleteSelected,
|
||||||
|
onErrorDelete = screenModel::delete,
|
||||||
onErrorSelected = screenModel::toggleSelection,
|
onErrorSelected = screenModel::toggleSelection,
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,26 @@ 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.addOrRemove
|
import eu.kanade.core.util.addOrRemove
|
||||||
import eu.kanade.presentation.libraryUpdateError.components.LibraryUpdateErrorUiModel
|
import eu.kanade.presentation.libraryUpdateError.components.LibraryUpdateErrorUiModel
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
|
import tachiyomi.domain.libraryUpdateError.interactor.DeleteLibraryUpdateErrors
|
||||||
import tachiyomi.domain.libraryUpdateError.interactor.GetLibraryUpdateErrorWithRelations
|
import tachiyomi.domain.libraryUpdateError.interactor.GetLibraryUpdateErrorWithRelations
|
||||||
import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations
|
import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations
|
||||||
import tachiyomi.domain.libraryUpdateErrorMessage.interactor.GetLibraryUpdateErrorMessages
|
import tachiyomi.domain.libraryUpdateErrorMessage.interactor.GetLibraryUpdateErrorMessages
|
||||||
import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage
|
import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class LibraryUpdateErrorScreenModel(
|
class LibraryUpdateErrorScreenModel(
|
||||||
private val getLibraryUpdateErrorWithRelations: GetLibraryUpdateErrorWithRelations = Injekt.get(),
|
private val getLibraryUpdateErrorWithRelations: GetLibraryUpdateErrorWithRelations = Injekt.get(),
|
||||||
private val getLibraryUpdateErrorMessages: GetLibraryUpdateErrorMessages = Injekt.get(),
|
private val getLibraryUpdateErrorMessages: GetLibraryUpdateErrorMessages = Injekt.get(),
|
||||||
|
private val deleteLibraryUpdateErrors: DeleteLibraryUpdateErrors = Injekt.get(),
|
||||||
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
) : StateScreenModel<LibraryUpdateErrorScreenState>(LibraryUpdateErrorScreenState()) {
|
) : StateScreenModel<LibraryUpdateErrorScreenState>(LibraryUpdateErrorScreenState()) {
|
||||||
|
|
||||||
// First and last selected index in list
|
// First and last selected index in list
|
||||||
|
|
@ -44,6 +51,7 @@ class LibraryUpdateErrorScreenModel(
|
||||||
return map { error ->
|
return map { error ->
|
||||||
LibraryUpdateErrorItem(
|
LibraryUpdateErrorItem(
|
||||||
error = error,
|
error = error,
|
||||||
|
sourceName = sourceManager.getOrStub(error.mangaSource).name,
|
||||||
selected = error.errorId in selectedErrorIds,
|
selected = error.errorId in selectedErrorIds,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -139,6 +147,26 @@ class LibraryUpdateErrorScreenModel(
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
selectedPositions[1] = -1
|
selectedPositions[1] = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun deleteSelected() {
|
||||||
|
launchIO {
|
||||||
|
deleteLibraryUpdateErrors.delete(selectedErrorIds.toList())
|
||||||
|
withUIContext {
|
||||||
|
selectedErrorIds.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun delete(errorId: Long) {
|
||||||
|
launchIO {
|
||||||
|
deleteLibraryUpdateErrors.delete(listOf(errorId))
|
||||||
|
withUIContext {
|
||||||
|
selectedErrorIds.remove(errorId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -156,7 +184,7 @@ data class LibraryUpdateErrorScreenState(
|
||||||
val errorMap = items.groupBy { it.error.messageId }
|
val errorMap = items.groupBy { it.error.messageId }
|
||||||
errorMap.forEach { (messageId, errors) ->
|
errorMap.forEach { (messageId, errors) ->
|
||||||
val message = messages.find { it.id == messageId }
|
val message = messages.find { it.id == messageId }
|
||||||
uiModels.add(LibraryUpdateErrorUiModel.Header(message!!.message))
|
uiModels.add(LibraryUpdateErrorUiModel.Header(message!!.message, errors.size))
|
||||||
uiModels.addAll(errors.map { LibraryUpdateErrorUiModel.Item(it) })
|
uiModels.addAll(errors.map { LibraryUpdateErrorUiModel.Item(it) })
|
||||||
}
|
}
|
||||||
return uiModels
|
return uiModels
|
||||||
|
|
@ -171,5 +199,14 @@ data class LibraryUpdateErrorScreenState(
|
||||||
@Immutable
|
@Immutable
|
||||||
data class LibraryUpdateErrorItem(
|
data class LibraryUpdateErrorItem(
|
||||||
val error: LibraryUpdateErrorWithRelations,
|
val error: LibraryUpdateErrorWithRelations,
|
||||||
|
val sourceName: String,
|
||||||
val selected: Boolean,
|
val selected: Boolean,
|
||||||
)
|
) {
|
||||||
|
val mangaCover = MangaCover(
|
||||||
|
mangaId = error.mangaId,
|
||||||
|
sourceId = error.mangaSource,
|
||||||
|
isMangaFavorite = error.favorite,
|
||||||
|
ogUrl = error.mangaThumbnail,
|
||||||
|
lastModified = error.coverLastModified,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,15 @@ class LibraryUpdateErrorRepositoryImpl(
|
||||||
return handler.await { libraryUpdateErrorQueries.deleteAllErrors() }
|
return handler.await { libraryUpdateErrorQueries.deleteAllErrors() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun delete(errorId: Long) {
|
override suspend fun delete(errorIds: List<Long>) {
|
||||||
return handler.await {
|
return handler.await {
|
||||||
libraryUpdateErrorQueries.deleteError(
|
libraryUpdateErrorQueries.deleteErrors(_ids = errorIds)
|
||||||
_id = errorId,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteMangaError(mangaId: Long) {
|
override suspend fun deleteMangaError(mangaIds: List<Long>) {
|
||||||
return handler.await {
|
return handler.await {
|
||||||
libraryUpdateErrorQueries.deleteMangaError(mangaId = mangaId)
|
libraryUpdateErrorQueries.deleteMangaErrors(mangaIds = mangaIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package tachiyomi.data.libraryUpdateError
|
package tachiyomi.data.libraryUpdateError
|
||||||
|
|
||||||
import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations
|
import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
|
||||||
|
|
||||||
val libraryUpdateErrorWithRelationsMapper:
|
val libraryUpdateErrorWithRelationsMapper:
|
||||||
(Long, String, Long, Boolean, String?, Long, Long, Long, Long) -> LibraryUpdateErrorWithRelations =
|
(Long, String, Long, Boolean, String?, Long, Long, Long, Long) -> LibraryUpdateErrorWithRelations =
|
||||||
|
|
@ -10,13 +9,9 @@ val libraryUpdateErrorWithRelationsMapper:
|
||||||
mangaId = mangaId,
|
mangaId = mangaId,
|
||||||
mangaTitle = mangaTitle,
|
mangaTitle = mangaTitle,
|
||||||
mangaSource = mangaSource,
|
mangaSource = mangaSource,
|
||||||
mangaCover = MangaCover(
|
favorite = favorite,
|
||||||
mangaId = mangaId,
|
mangaThumbnail = mangaThumbnail,
|
||||||
sourceId = mangaSource,
|
coverLastModified = coverLastModified,
|
||||||
isMangaFavorite = favorite,
|
|
||||||
ogUrl = mangaThumbnail,
|
|
||||||
lastModified = coverLastModified,
|
|
||||||
),
|
|
||||||
errorId = errorId,
|
errorId = errorId,
|
||||||
messageId = messageId,
|
messageId = messageId,
|
||||||
lastUpdate = lastUpdate,
|
lastUpdate = lastUpdate,
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,13 @@ WHERE manga_id = :mangaId;
|
||||||
deleteAllErrors:
|
deleteAllErrors:
|
||||||
DELETE FROM libraryUpdateError;
|
DELETE FROM libraryUpdateError;
|
||||||
|
|
||||||
deleteError:
|
deleteErrors:
|
||||||
DELETE FROM libraryUpdateError
|
DELETE FROM libraryUpdateError
|
||||||
WHERE _id = :_id;
|
WHERE _id IN :_ids;
|
||||||
|
|
||||||
deleteMangaError:
|
deleteMangaErrors:
|
||||||
DELETE FROM libraryUpdateError
|
DELETE FROM libraryUpdateError
|
||||||
WHERE manga_id = :mangaId;
|
WHERE manga_id IN :mangaIds;
|
||||||
|
|
||||||
cleanUnrelevantMangaErrors:
|
cleanUnrelevantMangaErrors:
|
||||||
DELETE FROM libraryUpdateError
|
DELETE FROM libraryUpdateError
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class DeleteLibraryUpdateErrors(
|
||||||
private val libraryUpdateErrorRepository: LibraryUpdateErrorRepository,
|
private val libraryUpdateErrorRepository: LibraryUpdateErrorRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await() = withNonCancellableContext {
|
suspend fun deleteAll() = withNonCancellableContext {
|
||||||
try {
|
try {
|
||||||
libraryUpdateErrorRepository.deleteAll()
|
libraryUpdateErrorRepository.deleteAll()
|
||||||
Result.Success
|
Result.Success
|
||||||
|
|
@ -19,9 +19,9 @@ class DeleteLibraryUpdateErrors(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun await(errorId: Long) = withNonCancellableContext {
|
suspend fun delete(errorIds: List<Long>) = withNonCancellableContext {
|
||||||
try {
|
try {
|
||||||
libraryUpdateErrorRepository.delete(errorId)
|
libraryUpdateErrorRepository.delete(errorIds)
|
||||||
Result.Success
|
Result.Success
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e)
|
logcat(LogPriority.ERROR, e)
|
||||||
|
|
@ -29,9 +29,9 @@ class DeleteLibraryUpdateErrors(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteMangaError(mangaId: Long) = withNonCancellableContext {
|
suspend fun deleteMangaError(mangaIds: List<Long>) = withNonCancellableContext {
|
||||||
try {
|
try {
|
||||||
libraryUpdateErrorRepository.deleteMangaError(mangaId)
|
libraryUpdateErrorRepository.deleteMangaError(mangaIds)
|
||||||
Result.Success
|
Result.Success
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e)
|
logcat(LogPriority.ERROR, e)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package tachiyomi.domain.libraryUpdateError.model
|
package tachiyomi.domain.libraryUpdateError.model
|
||||||
|
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
|
||||||
|
|
||||||
data class LibraryUpdateErrorWithRelations(
|
data class LibraryUpdateErrorWithRelations(
|
||||||
val mangaId: Long,
|
val mangaId: Long,
|
||||||
val mangaTitle: String,
|
val mangaTitle: String,
|
||||||
val mangaSource: Long,
|
val mangaSource: Long,
|
||||||
val mangaCover: MangaCover,
|
val favorite: Boolean,
|
||||||
|
val mangaThumbnail: String?,
|
||||||
|
val coverLastModified: Long,
|
||||||
val errorId: Long,
|
val errorId: Long,
|
||||||
val messageId: Long,
|
val messageId: Long,
|
||||||
val lastUpdate: Long,
|
val lastUpdate: Long,
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ interface LibraryUpdateErrorRepository {
|
||||||
|
|
||||||
suspend fun deleteAll()
|
suspend fun deleteAll()
|
||||||
|
|
||||||
suspend fun delete(errorId: Long)
|
suspend fun delete(errorIds: List<Long>)
|
||||||
|
|
||||||
suspend fun deleteMangaError(mangaId: Long)
|
suspend fun deleteMangaError(mangaIds: List<Long>)
|
||||||
|
|
||||||
suspend fun cleanUnrelevantMangaErrors()
|
suspend fun cleanUnrelevantMangaErrors()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
package tachiyomi.presentation.core.components
|
package tachiyomi.presentation.core.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Badge
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
|
@ -18,6 +21,7 @@ fun ListGroupHeader(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
tonalElevation: Dp = 0.dp,
|
tonalElevation: Dp = 0.dp,
|
||||||
|
count: Int? = null,
|
||||||
// KMK <--
|
// KMK <--
|
||||||
) {
|
) {
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
|
@ -25,6 +29,10 @@ fun ListGroupHeader(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
tonalElevation = tonalElevation,
|
tonalElevation = tonalElevation,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
// KMK <--
|
// KMK <--
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -38,5 +46,16 @@ fun ListGroupHeader(
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
)
|
||||||
|
// KMK -->
|
||||||
|
if (count != null) {
|
||||||
|
Badge(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
) {
|
||||||
|
Text("$count")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue