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:
Cuong-Tran 2025-05-17 19:07:42 +07:00 committed by GitHub
parent bc33a75b6b
commit 2024cdf168
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 225 additions and 84 deletions

View file

@ -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,

View file

@ -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()
} }

View file

@ -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,

View file

@ -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?>) {

View file

@ -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,
) )

View file

@ -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,
)
}

View file

@ -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)
} }
} }

View file

@ -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,

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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()

View file

@ -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 <--
}
} }
} }