From bc460dd3b1edf67d095df0b15c6d1dc39444d5e6 Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Sun, 27 Oct 2024 02:21:10 +0700 Subject: [PATCH] Error screen improve (#462) - A dedicated screen in Settings for error list when updating library - Allow to jump to Error screen if click on notification - Allow to migrate error entry - Create error on each entry updated instead of waiting for the whole updating list to finished - Overwrite entry's error if new error happens after updating - Clear entry's error if it successfully updated - Clear un-relevant errors (entry which was removed from Library) on next update - List of errors can jump to top/bottom or next/previous errors group - Won't create error file anymore * Added library update errors screen (cherry picked from commit 7cf37d52f959ac65f53cf7657563fb4428bd9188) * Open library update errors screen on clicking library update error notification (cherry picked from commit d2d22f437a1d61b086f1e19dfbcd2c0a2bc125f4) * LibraryUpdateErrorScreen's bottom UI with scroll to top/bottom buttons (cherry picked from commit 859ce54474d456232510e21f4f6795af65489be2) * migrate to AppBar * sticky header * scroll to next/previous group of errors * insert error entry one by one * delete error from DB when successfully updated * clean un-relevant errors from DB on every updates * fix errors & clean up * catch exception & fix notification intent --------- Co-authored-by: ImaginaryDesignation <108343184+ImaginaryDesignation@users.noreply.github.com> --- .../java/eu/kanade/domain/DomainModule.kt | 28 -- .../java/eu/kanade/domain/KMKDomainModule.kt | 40 +++ .../LibraryUpdateErrorScreen.kt | 317 ++++++++++++++---- .../components/LibraryUpdateErrorUiItem.kt | 103 +++--- .../eu/kanade/presentation/more/MoreScreen.kt | 13 +- app/src/main/java/eu/kanade/tachiyomi/App.kt | 4 + .../data/library/LibraryUpdateJob.kt | 64 ++-- .../data/library/LibraryUpdateNotifier.kt | 6 +- .../data/notification/NotificationReceiver.kt | 21 ++ .../eu/kanade/tachiyomi/ui/home/HomeScreen.kt | 18 +- .../LibraryUpdateErrorScreenModel.kt | 5 + .../kanade/tachiyomi/ui/main/MainActivity.kt | 6 + .../eu/kanade/tachiyomi/ui/more/MoreTab.kt | 4 +- .../kotlin/tachiyomi/core/common/Constants.kt | 4 + .../LibraryUpdateErrorRepositoryImpl.kt | 23 ++ ...LibraryUpdateErrorMessageRepositoryImpl.kt | 14 +- .../tachiyomi/data/libraryUpdateError.sq | 26 +- .../data/libraryUpdateErrorMessage.sq | 4 + .../sqldelight/tachiyomi/migrations/35.sqm | 2 +- .../interactor/DeleteLibraryUpdateErrors.kt | 22 +- .../interactor/InsertLibraryUpdateErrors.kt | 34 +- .../LibraryUpdateErrorRepository.kt | 6 + .../DeleteLibraryUpdateErrorMessages.kt | 2 +- .../InsertLibraryUpdateErrorMessages.kt | 5 +- .../LibraryUpdateErrorMessageRepository.kt | 4 +- .../moko-resources/base/strings.xml | 4 + .../core/components/ListGroupHeader.kt | 30 +- 27 files changed, 579 insertions(+), 230 deletions(-) create mode 100644 app/src/main/java/eu/kanade/domain/KMKDomainModule.kt diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index 4968297556..eba1fe6a28 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -37,9 +37,6 @@ import mihon.domain.upcoming.interactor.GetUpcomingManga import tachiyomi.data.category.CategoryRepositoryImpl import tachiyomi.data.chapter.ChapterRepositoryImpl import tachiyomi.data.history.HistoryRepositoryImpl -import tachiyomi.data.libraryUpdateError.LibraryUpdateErrorRepositoryImpl -import tachiyomi.data.libraryUpdateError.LibraryUpdateErrorWithRelationsRepositoryImpl -import tachiyomi.data.libraryUpdateErrorMessage.LibraryUpdateErrorMessageRepositoryImpl import tachiyomi.data.manga.MangaRepositoryImpl import tachiyomi.data.release.ReleaseServiceImpl import tachiyomi.data.source.SourceRepositoryImpl @@ -70,16 +67,6 @@ import tachiyomi.domain.history.interactor.GetTotalReadDuration import tachiyomi.domain.history.interactor.RemoveHistory import tachiyomi.domain.history.interactor.UpsertHistory import tachiyomi.domain.history.repository.HistoryRepository -import tachiyomi.domain.libraryUpdateError.interactor.DeleteLibraryUpdateErrors -import tachiyomi.domain.libraryUpdateError.interactor.GetLibraryUpdateErrorWithRelations -import tachiyomi.domain.libraryUpdateError.interactor.GetLibraryUpdateErrors -import tachiyomi.domain.libraryUpdateError.interactor.InsertLibraryUpdateErrors -import tachiyomi.domain.libraryUpdateError.repository.LibraryUpdateErrorRepository -import tachiyomi.domain.libraryUpdateError.repository.LibraryUpdateErrorWithRelationsRepository -import tachiyomi.domain.libraryUpdateErrorMessage.interactor.DeleteLibraryUpdateErrorMessages -import tachiyomi.domain.libraryUpdateErrorMessage.interactor.GetLibraryUpdateErrorMessages -import tachiyomi.domain.libraryUpdateErrorMessage.interactor.InsertLibraryUpdateErrorMessages -import tachiyomi.domain.libraryUpdateErrorMessage.repository.LibraryUpdateErrorMessageRepository import tachiyomi.domain.manga.interactor.FetchInterval import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga import tachiyomi.domain.manga.interactor.GetFavorites @@ -183,21 +170,6 @@ class DomainModule : InjektModule { addSingletonFactory { UpdatesRepositoryImpl(get()) } addFactory { GetUpdates(get()) } - addSingletonFactory { - LibraryUpdateErrorWithRelationsRepositoryImpl(get()) - } - addFactory { GetLibraryUpdateErrorWithRelations(get()) } - - addSingletonFactory { LibraryUpdateErrorMessageRepositoryImpl(get()) } - addFactory { GetLibraryUpdateErrorMessages(get()) } - addFactory { DeleteLibraryUpdateErrorMessages(get()) } - addFactory { InsertLibraryUpdateErrorMessages(get()) } - - addSingletonFactory { LibraryUpdateErrorRepositoryImpl(get()) } - addFactory { GetLibraryUpdateErrors(get()) } - addFactory { DeleteLibraryUpdateErrors(get()) } - addFactory { InsertLibraryUpdateErrors(get()) } - addSingletonFactory { SourceRepositoryImpl(get(), get()) } addSingletonFactory { StubSourceRepositoryImpl(get()) } addFactory { GetEnabledSources(get(), get()) } diff --git a/app/src/main/java/eu/kanade/domain/KMKDomainModule.kt b/app/src/main/java/eu/kanade/domain/KMKDomainModule.kt new file mode 100644 index 0000000000..e80a71f794 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/KMKDomainModule.kt @@ -0,0 +1,40 @@ +package eu.kanade.domain + +import tachiyomi.data.libraryUpdateError.LibraryUpdateErrorRepositoryImpl +import tachiyomi.data.libraryUpdateError.LibraryUpdateErrorWithRelationsRepositoryImpl +import tachiyomi.data.libraryUpdateErrorMessage.LibraryUpdateErrorMessageRepositoryImpl +import tachiyomi.domain.libraryUpdateError.interactor.DeleteLibraryUpdateErrors +import tachiyomi.domain.libraryUpdateError.interactor.GetLibraryUpdateErrorWithRelations +import tachiyomi.domain.libraryUpdateError.interactor.GetLibraryUpdateErrors +import tachiyomi.domain.libraryUpdateError.interactor.InsertLibraryUpdateErrors +import tachiyomi.domain.libraryUpdateError.repository.LibraryUpdateErrorRepository +import tachiyomi.domain.libraryUpdateError.repository.LibraryUpdateErrorWithRelationsRepository +import tachiyomi.domain.libraryUpdateErrorMessage.interactor.DeleteLibraryUpdateErrorMessages +import tachiyomi.domain.libraryUpdateErrorMessage.interactor.GetLibraryUpdateErrorMessages +import tachiyomi.domain.libraryUpdateErrorMessage.interactor.InsertLibraryUpdateErrorMessages +import tachiyomi.domain.libraryUpdateErrorMessage.repository.LibraryUpdateErrorMessageRepository +import uy.kohesive.injekt.api.InjektModule +import uy.kohesive.injekt.api.InjektRegistrar +import uy.kohesive.injekt.api.addFactory +import uy.kohesive.injekt.api.addSingletonFactory +import uy.kohesive.injekt.api.get + +class KMKDomainModule : InjektModule { + + override fun InjektRegistrar.registerInjectables() { + addSingletonFactory { + LibraryUpdateErrorWithRelationsRepositoryImpl(get()) + } + addFactory { GetLibraryUpdateErrorWithRelations(get()) } + + addSingletonFactory { LibraryUpdateErrorMessageRepositoryImpl(get()) } + addFactory { GetLibraryUpdateErrorMessages(get()) } + addFactory { DeleteLibraryUpdateErrorMessages(get()) } + addFactory { InsertLibraryUpdateErrorMessages(get()) } + + addSingletonFactory { LibraryUpdateErrorRepositoryImpl(get()) } + addFactory { GetLibraryUpdateErrors(get()) } + addFactory { DeleteLibraryUpdateErrors(get()) } + addFactory { InsertLibraryUpdateErrors(get()) } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/libraryUpdateError/LibraryUpdateErrorScreen.kt b/app/src/main/java/eu/kanade/presentation/libraryUpdateError/LibraryUpdateErrorScreen.kt index ff69081ea3..d2adc2c51a 100644 --- a/app/src/main/java/eu/kanade/presentation/libraryUpdateError/LibraryUpdateErrorScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/libraryUpdateError/LibraryUpdateErrorScreen.kt @@ -2,6 +2,7 @@ package eu.kanade.presentation.libraryUpdateError import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn @@ -20,21 +21,28 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.ZeroCornerSize import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowDownward +import androidx.compose.material.icons.outlined.ArrowUpward import androidx.compose.material.icons.outlined.FindReplace import androidx.compose.material.icons.outlined.FlipToBack import androidx.compose.material.icons.outlined.SelectAll +import androidx.compose.material.icons.outlined.VerticalAlignBottom +import androidx.compose.material.icons.outlined.VerticalAlignTop import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.ripple import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -45,9 +53,11 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.libraryUpdateError.components.libraryUpdateErrorUiItems import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorItem import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorScreenState +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.isActive @@ -73,6 +83,36 @@ fun LibraryUpdateErrorScreen( onErrorSelected: (LibraryUpdateErrorItem, Boolean, Boolean, Boolean) -> Unit, navigateUp: () -> Unit, ) { + val listState = rememberLazyListState() + val scope = rememberCoroutineScope() + + val enableScrollToTop by remember { + derivedStateOf { + listState.firstVisibleItemIndex > 0 + } + } + + val enableScrollToBottom by remember { + derivedStateOf { + listState.canScrollForward + } + } + + val headerIndexes = remember { mutableStateOf>(emptyList()) } + LaunchedEffect(state) { + headerIndexes.value = state.getHeaderIndexes() + } + val enableScrollToPrevious by remember { + derivedStateOf { + headerIndexes.value.any { it < listState.firstVisibleItemIndex } + } + } + val enableScrollToNext by remember { + derivedStateOf { + headerIndexes.value.any { it > listState.firstVisibleItemIndex } + } + } + BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) }) Scaffold( @@ -82,72 +122,67 @@ fun LibraryUpdateErrorScreen( KMR.strings.label_library_update_errors, state.items.size, ), - actionModeCounter = state.selected.size, - onSelectAll = { onSelectAll(true) }, - onInvertSelection = onInvertSelection, - onCancelActionMode = { onSelectAll(false) }, - scrollBehavior = scrollBehavior, + itemCnt = state.items.size, navigateUp = navigateUp, + selectedCount = state.selected.size, + onClickUnselectAll = { onSelectAll(false) }, + onClickSelectAll = { onSelectAll(true) }, + onClickInvertSelection = onInvertSelection, + scrollBehavior = scrollBehavior, ) }, bottomBar = { - AnimatedVisibility( - visible = state.selected.isNotEmpty(), - enter = expandVertically(expandFrom = Alignment.Bottom), - exit = shrinkVertically(shrinkTowards = Alignment.Bottom), - ) { - val scope = rememberCoroutineScope() - Surface( - modifier = modifier, - shape = MaterialTheme.shapes.large.copy( - bottomEnd = ZeroCornerSize, - bottomStart = ZeroCornerSize, - ), - tonalElevation = 3.dp, - ) { - val haptic = LocalHapticFeedback.current - val confirm = remember { mutableStateListOf(false) } - var resetJob: Job? = remember { null } - val onLongClickItem: (Int) -> Unit = { toConfirmIndex -> - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - (0 until 1).forEach { i -> confirm[i] = i == toConfirmIndex } - resetJob?.cancel() - resetJob = scope.launch { - delay(1.seconds) - if (isActive) confirm[toConfirmIndex] = false - } + LibraryUpdateErrorsBottomBar( + modifier = modifier, + selected = state.selected, + onMultiMigrateClicked = onMultiMigrateClicked, + enableScrollToTop = enableScrollToTop, + enableScrollToBottom = enableScrollToBottom, + scrollToTop = { + scope.launch { + listState.scrollToItem(0) + } + }, + scrollToBottom = { + scope.launch { + listState.scrollToItem(state.items.size - 1) } - Row( - modifier = Modifier - .padding( - WindowInsets.navigationBars - .only(WindowInsetsSides.Bottom) - .asPaddingValues(), - ) - .padding(horizontal = 8.dp, vertical = 12.dp), - ) { - Button( - title = stringResource(MR.strings.migrate), - icon = Icons.Outlined.FindReplace, - toConfirm = confirm[0], - onLongClick = { onLongClickItem(0) }, - onClick = onMultiMigrateClicked, + }, + enableScrollToPrevious = enableScrollToTop && enableScrollToPrevious, + enableScrollToNext = enableScrollToBottom && enableScrollToNext, + scrollToPrevious = { + scope.launch { + listState.scrollToItem( + state.getHeaderIndexes() + .filter { it < listState.firstVisibleItemIndex } + .maxOrNull() ?: 0, ) } - } - } + }, + scrollToNext = { + scope.launch { + listState.scrollToItem( + state.getHeaderIndexes() + .filter { it > listState.firstVisibleItemIndex } + .minOrNull() ?: 0, + ) + } + }, + ) }, - ) { paddingValues -> + ) { contentPadding -> when { - state.isLoading -> LoadingScreen(modifier = Modifier.padding(paddingValues)) + state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) state.items.isEmpty() -> EmptyScreen( message = stringResource(KMR.strings.info_empty_library_update_errors), - modifier = Modifier.padding(paddingValues), + modifier = Modifier.padding(contentPadding), ) else -> { FastScrollLazyColumn( - contentPadding = paddingValues, + // Using modifier instead of contentPadding so we can use stickyHeader + modifier = Modifier.padding(contentPadding), + state = listState, ) { libraryUpdateErrorUiItems( uiModels = state.getUiModel(), @@ -162,16 +197,134 @@ fun LibraryUpdateErrorScreen( } } +@Composable +private fun LibraryUpdateErrorsBottomBar( + modifier: Modifier = Modifier, + selected: List, + onMultiMigrateClicked: (() -> Unit), + enableScrollToTop: Boolean, + enableScrollToBottom: Boolean, + scrollToTop: () -> Unit, + scrollToBottom: () -> Unit, + enableScrollToPrevious: Boolean, + enableScrollToNext: Boolean, + scrollToPrevious: () -> Unit, + scrollToNext: () -> Unit, +) { + val scope = rememberCoroutineScope() + Surface( + modifier = modifier, + shape = MaterialTheme.shapes.large.copy( + bottomEnd = ZeroCornerSize, + bottomStart = ZeroCornerSize, + ), + color = MaterialTheme.colorScheme.surfaceContainerHigh, + ) { + val haptic = LocalHapticFeedback.current + val confirm = remember { mutableStateListOf(false, false, false, false, false) } + var resetJob: Job? = remember { null } + val onLongClickItem: (Int) -> Unit = { toConfirmIndex -> + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + (0 until 5).forEach { i -> confirm[i] = i == toConfirmIndex } + resetJob?.cancel() + resetJob = scope.launch { + delay(1.seconds) + if (isActive) confirm[toConfirmIndex] = false + } + } + Row( + modifier = Modifier + .padding( + WindowInsets.navigationBars + .only(WindowInsetsSides.Bottom) + .asPaddingValues(), + ) + .padding(horizontal = 8.dp, vertical = 12.dp), + ) { + Button( + title = stringResource(KMR.strings.action_scroll_to_top), + icon = Icons.Outlined.VerticalAlignTop, + toConfirm = confirm[0], + onLongClick = { onLongClickItem(0) }, + onClick = if (enableScrollToTop) { + scrollToTop + } else { + {} + }, + enabled = enableScrollToTop, + ) + Button( + title = stringResource(KMR.strings.action_scroll_to_previous), + icon = Icons.Outlined.ArrowUpward, + toConfirm = confirm[1], + onLongClick = { onLongClickItem(1) }, + onClick = if (enableScrollToPrevious) { + scrollToPrevious + } else { + {} + }, + enabled = enableScrollToPrevious, + ) + Button( + title = stringResource(MR.strings.migrate), + icon = Icons.Outlined.FindReplace, + toConfirm = confirm[2], + onLongClick = { onLongClickItem(2) }, + onClick = if (selected.isNotEmpty()) { + onMultiMigrateClicked + } else { + {} + }, + enabled = selected.isNotEmpty(), + ) + Button( + title = stringResource(KMR.strings.action_scroll_to_next), + icon = Icons.Outlined.ArrowDownward, + toConfirm = confirm[3], + onLongClick = { onLongClickItem(3) }, + onClick = if (enableScrollToNext) { + scrollToNext + } else { + {} + }, + enabled = enableScrollToNext, + ) + Button( + title = stringResource(KMR.strings.action_scroll_to_bottom), + icon = Icons.Outlined.VerticalAlignBottom, + toConfirm = confirm[4], + onLongClick = { onLongClickItem(4) }, + onClick = if (enableScrollToBottom) { + scrollToBottom + } else { + {} + }, + enabled = enableScrollToBottom, + ) + } + } +} + @Composable private fun RowScope.Button( title: String, icon: ImageVector, toConfirm: Boolean, + enabled: Boolean, onLongClick: () -> Unit, onClick: (() -> Unit), content: (@Composable () -> Unit)? = null, ) { val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f) + val animatedColor by animateColorAsState( + if (enabled) { + MaterialTheme.colorScheme.onSurface + } else { + MaterialTheme.colorScheme.onSurface.copy( + alpha = 0.38f, + ) + }, + ) Column( modifier = Modifier .size(48.dp) @@ -188,6 +341,7 @@ private fun RowScope.Button( Icon( imageVector = icon, contentDescription = title, + tint = animatedColor, ) AnimatedVisibility( visible = toConfirm, @@ -199,6 +353,7 @@ private fun RowScope.Button( overflow = TextOverflow.Visible, maxLines = 1, style = MaterialTheme.typography.labelSmall, + color = animatedColor, ) } content?.invoke() @@ -207,35 +362,49 @@ private fun RowScope.Button( @Composable private fun LibraryUpdateErrorsAppBar( - modifier: Modifier = Modifier, title: String, - actionModeCounter: Int, - onSelectAll: () -> Unit, - onInvertSelection: () -> Unit, - onCancelActionMode: () -> Unit, - scrollBehavior: TopAppBarScrollBehavior, + itemCnt: Int, navigateUp: () -> Unit, + selectedCount: Int, + onClickUnselectAll: () -> Unit, + onClickSelectAll: () -> Unit, + onClickInvertSelection: () -> Unit, + scrollBehavior: TopAppBarScrollBehavior, ) { AppBar( - modifier = modifier, title = title, - scrollBehavior = scrollBehavior, - actionModeCounter = actionModeCounter, - onCancelActionMode = onCancelActionMode, - actionModeActions = { - IconButton(onClick = onSelectAll) { - Icon( - imageVector = Icons.Outlined.SelectAll, - contentDescription = stringResource(MR.strings.action_select_all), - ) - } - IconButton(onClick = onInvertSelection) { - Icon( - imageVector = Icons.Outlined.FlipToBack, - contentDescription = stringResource(MR.strings.action_select_inverse), + navigateUp = navigateUp, + actions = { + if (itemCnt > 0) { + AppBarActions( + persistentListOf( + AppBar.Action( + title = stringResource(MR.strings.action_select_all), + icon = Icons.Outlined.SelectAll, + onClick = onClickSelectAll, + ), + ), ) } }, - navigateUp = navigateUp, + actionModeCounter = selectedCount, + onCancelActionMode = onClickUnselectAll, + actionModeActions = { + 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, + ), + ), + ) + }, + scrollBehavior = scrollBehavior, ) } diff --git a/app/src/main/java/eu/kanade/presentation/libraryUpdateError/components/LibraryUpdateErrorUiItem.kt b/app/src/main/java/eu/kanade/presentation/libraryUpdateError/components/LibraryUpdateErrorUiItem.kt index 6ba3fe25aa..f8d68da93a 100644 --- a/app/src/main/java/eu/kanade/presentation/libraryUpdateError/components/LibraryUpdateErrorUiItem.kt +++ b/app/src/main/java/eu/kanade/presentation/libraryUpdateError/components/LibraryUpdateErrorUiItem.kt @@ -3,11 +3,9 @@ package eu.kanade.presentation.libraryUpdateError.components import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -18,11 +16,14 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import eu.kanade.presentation.manga.components.MangaCover +import eu.kanade.presentation.util.animateItemFastScroll import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorItem import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations import tachiyomi.domain.source.service.SourceManager import tachiyomi.presentation.core.components.ListGroupHeader +import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.selectedBackground import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -34,57 +35,52 @@ internal fun LazyListScope.libraryUpdateErrorUiItems( onClick: (LibraryUpdateErrorItem) -> Unit, onClickCover: (LibraryUpdateErrorItem) -> Unit, ) { - items( - items = uiModels, - contentType = { - when (it) { - is LibraryUpdateErrorUiModel.Header -> "header" - is LibraryUpdateErrorUiModel.Item -> "item" - } - }, - key = { - when (it) { - is LibraryUpdateErrorUiModel.Header -> "sticky:errorHeader-${it.hashCode()}" - is LibraryUpdateErrorUiModel.Item -> "error-${it.item.error.errorId}-${it.item.error.mangaId}" - } - }, - ) { item -> - when (item) { + uiModels.forEach { + when (it) { is LibraryUpdateErrorUiModel.Header -> { - ListGroupHeader( - modifier = Modifier.animateItemPlacement(), - text = item.errorMessage, - ) + stickyHeader( + key = "$STICKY_HEADER_KEY_PREFIX-errorHeader-${it.hashCode()}", + contentType = "header", + ) { + ListGroupHeader( + modifier = Modifier.animateItemFastScroll(), + text = it.errorMessage, + ) + } } - is LibraryUpdateErrorUiModel.Item -> { - val libraryUpdateErrorItem = item.item - LibraryUpdateErrorUiItem( - modifier = Modifier.animateItemPlacement(), - error = libraryUpdateErrorItem.error, - selected = libraryUpdateErrorItem.selected, - onClick = { - when { - selectionMode -> onErrorSelected( + item( + key = "error-${it.item.error.errorId}-${it.item.error.mangaId}", + contentType = "item", + ) { + val libraryUpdateErrorItem = it.item + LibraryUpdateErrorUiItem( + modifier = Modifier.animateItemFastScroll(), + error = libraryUpdateErrorItem.error, + selected = libraryUpdateErrorItem.selected, + onClick = { + when { + selectionMode -> onErrorSelected( + libraryUpdateErrorItem, + !libraryUpdateErrorItem.selected, + true, + false, + ) + + else -> onClick(libraryUpdateErrorItem) + } + }, + onLongClick = { + onErrorSelected( libraryUpdateErrorItem, !libraryUpdateErrorItem.selected, true, - false, + true, ) - - else -> onClick(libraryUpdateErrorItem) - } - }, - onLongClick = { - onErrorSelected( - libraryUpdateErrorItem, - !libraryUpdateErrorItem.selected, - true, - true, - ) - }, - onClickCover = { onClickCover(libraryUpdateErrorItem) }.takeIf { !selectionMode }, - ) + }, + onClickCover = { onClickCover(libraryUpdateErrorItem) }.takeIf { !selectionMode }, + ) + } } } } @@ -111,37 +107,36 @@ private fun LibraryUpdateErrorUiItem( haptic.performHapticFeedback(HapticFeedbackType.LongPress) }, ) - .height(56.dp) .padding(horizontal = MaterialTheme.padding.medium), - verticalAlignment = Alignment.CenterVertically, + verticalAlignment = Alignment.Top, ) { MangaCover.Square( modifier = Modifier .padding(vertical = 6.dp) - .fillMaxHeight(), + .height(48.dp), data = error.mangaCover, onClick = onClickCover, ) Column( modifier = Modifier - .padding(horizontal = MaterialTheme.padding.medium) + .padding(horizontal = MaterialTheme.padding.medium, vertical = 5.dp) .weight(1f), ) { Text( text = error.mangaTitle, style = MaterialTheme.typography.bodyMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, + overflow = TextOverflow.Visible, ) - Row(verticalAlignment = Alignment.CenterVertically) { + Row(modifier = Modifier.padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically) { Text( text = Injekt.get().getOrStub(error.mangaSource).name, style = MaterialTheme.typography.bodySmall, - overflow = TextOverflow.Ellipsis, + overflow = TextOverflow.Visible, maxLines = 1, modifier = Modifier + .secondaryItemAlpha() .weight(weight = 1f, fill = false), ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt index c885c3cfa8..0bc8cc9739 100644 --- a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt @@ -54,13 +54,15 @@ fun MoreScreen( onClickDownloadQueue: () -> Unit, onClickCategories: () -> Unit, onClickStats: () -> Unit, - onClickLibraryUpdateErrors: () -> Unit, onClickDataAndStorage: () -> Unit, onClickSettings: () -> Unit, onClickAbout: () -> Unit, onClickBatchAdd: () -> Unit, onClickUpdates: () -> Unit, onClickHistory: () -> Unit, + // KMK --> + onClickLibraryUpdateErrors: () -> Unit, + // KMK <-- ) { val uriHandler = LocalUriHandler.current @@ -176,6 +178,15 @@ fun MoreScreen( onPreferenceClick = onClickStats, ) } + // KMK --> + item { + TextPreferenceWidget( + title = stringResource(KMR.strings.option_label_library_update_errors), + icon = Icons.Outlined.NewReleases, + onPreferenceClick = onClickLibraryUpdateErrors, + ) + } + // KMK <-- item { TextPreferenceWidget( title = stringResource(KMR.strings.option_label_library_update_errors), diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 4676398150..94ea7a13ae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -30,6 +30,7 @@ import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator import dev.mihon.injekt.patchInjekt import eu.kanade.domain.DomainModule +import eu.kanade.domain.KMKDomainModule import eu.kanade.domain.SYDomainModule import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.sync.SyncPreferences @@ -125,6 +126,9 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor Injekt.importModule(SYPreferenceModule(this)) Injekt.importModule(SYDomainModule()) // SY <-- + // KMK --> + Injekt.importModule(KMKDomainModule()) + // KMK <-- setupExhLogging() // EXH logging LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index a2b5dfae6a..d503128bfa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -35,8 +35,6 @@ import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.util.prepUpdateCover -import eu.kanade.tachiyomi.util.storage.getUriCompat -import eu.kanade.tachiyomi.util.system.createFileInCacheDir import eu.kanade.tachiyomi.util.system.isConnectedToWifi import eu.kanade.tachiyomi.util.system.isRunning import eu.kanade.tachiyomi.util.system.setForegroundSafely @@ -82,7 +80,6 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSI import tachiyomi.domain.libraryUpdateError.interactor.DeleteLibraryUpdateErrors import tachiyomi.domain.libraryUpdateError.interactor.InsertLibraryUpdateErrors import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateError -import tachiyomi.domain.libraryUpdateErrorMessage.interactor.DeleteLibraryUpdateErrorMessages import tachiyomi.domain.libraryUpdateErrorMessage.interactor.InsertLibraryUpdateErrorMessages import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage import tachiyomi.domain.manga.interactor.FetchInterval @@ -101,7 +98,6 @@ import tachiyomi.domain.track.interactor.InsertTrack import tachiyomi.i18n.MR import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.io.File import java.time.Instant import java.time.ZonedDateTime import java.util.concurrent.CopyOnWriteArrayList @@ -120,10 +116,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private val getManga: GetManga = Injekt.get() private val updateManga: UpdateManga = Injekt.get() private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get() - private val deleteLibraryUpdateErrorMessages: DeleteLibraryUpdateErrorMessages = Injekt.get() - private val deleteLibraryUpdateErrors: DeleteLibraryUpdateErrors = Injekt.get() - private val insertLibraryUpdateErrors: InsertLibraryUpdateErrors = Injekt.get() - private val insertLibraryUpdateErrorMessages: InsertLibraryUpdateErrorMessages = Injekt.get() private val fetchInterval: FetchInterval = Injekt.get() private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get() @@ -144,6 +136,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet // KMK --> private val libraryUpdateStatus: LibraryUpdateStatus = Injekt.get() + private val deleteLibraryUpdateErrors: DeleteLibraryUpdateErrors = Injekt.get() + private val insertLibraryUpdateErrors: InsertLibraryUpdateErrors = Injekt.get() + private val insertLibraryUpdateErrorMessages: InsertLibraryUpdateErrorMessages = Injekt.get() // KMK <-- private var mangaToUpdate: List = mutableListOf() @@ -164,6 +159,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet // KMK --> libraryUpdateStatus.start() + + deleteLibraryUpdateErrors.cleanUnrelevantMangaErrors() // KMK <-- setForegroundSafely() @@ -463,6 +460,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet // Convert to the manga that contains new chapters newUpdates.add(manga to newChapters.toTypedArray()) } + clearErrorFromDB(mangaId = manga.id) } catch (e: Throwable) { val errorMessage = when (e) { is NoChaptersException -> @@ -474,6 +472,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet ) else -> e.message } + writeErrorToDB(manga to errorMessage) failedUpdates.add(manga to errorMessage) } } @@ -494,11 +493,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet } if (failedUpdates.isNotEmpty()) { - writeErrorsToDB(failedUpdates) - val errorFile = writeErrorFile(failedUpdates) notifier.showUpdateErrorNotification( failedUpdates.size, - errorFile.getUriCompat(context), ) } } @@ -715,39 +711,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet ) } - /** - * Writes basic file of update errors to cache dir. - */ - private fun writeErrorFile(errors: List>): File { - try { - if (errors.isNotEmpty()) { - val file = context.createFileInCacheDir("komikku_update_errors.txt") - file.bufferedWriter().use { out -> - out.write(context.stringResource(MR.strings.library_errors_help, ERROR_LOG_HELP_URL) + "\n\n") - // Error file format: - // ! Error - // # Source - // - Manga - errors.groupBy({ it.second }, { it.first }).forEach { (error, mangas) -> - out.write("\n! ${error}\n") - mangas.groupBy { it.source }.forEach { (srcId, mangas) -> - val source = sourceManager.getOrStub(srcId) - out.write(" # $source\n") - mangas.forEach { - out.write(" - ${it.title}\n") - } - } - } - } - return file - } - } catch (_: Exception) {} - return File("") + // KMK --> + private suspend fun clearErrorFromDB(mangaId: Long) { + deleteLibraryUpdateErrors.deleteMangaError(mangaId = mangaId) + } + + private suspend fun writeErrorToDB(error: Pair) { + val errorMessage = error.second ?: "???" + val errorMessageId = insertLibraryUpdateErrorMessages.get(errorMessage) + ?: insertLibraryUpdateErrorMessages.insert( + libraryUpdateErrorMessage = LibraryUpdateErrorMessage(-1L, errorMessage), + ) + + insertLibraryUpdateErrors.upsert( + LibraryUpdateError(id = -1L, mangaId = error.first.id, messageId = errorMessageId), + ) } private suspend fun writeErrorsToDB(errors: List>) { - deleteLibraryUpdateErrorMessages.await() - deleteLibraryUpdateErrors.await() val libraryErrors = errors.groupBy({ it.second }, { it.first }) val errorMessages = insertLibraryUpdateErrorMessages.insertAll( libraryUpdateErrorMessages = libraryErrors.keys.map { errorMessage -> @@ -762,6 +743,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet } insertLibraryUpdateErrors.insertAll(errorList) } + // KMK <-- /** * Defines what should be updated within a service execution. diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index 766ad62494..6e246f7287 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -6,7 +6,6 @@ import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.net.Uri import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import coil3.asDrawable @@ -152,9 +151,8 @@ class LibraryUpdateNotifier( * Shows notification containing update entries that failed with action to open full log. * * @param failed Number of entries that failed to update. - * @param uri Uri for error log file containing all titles that failed. */ - fun showUpdateErrorNotification(failed: Int, uri: Uri) { + fun showUpdateErrorNotification(failed: Int) { if (failed == 0) { return } @@ -167,7 +165,7 @@ class LibraryUpdateNotifier( setContentText(context.stringResource(MR.strings.action_show_errors)) setSmallIcon(R.drawable.ic_komikku) - setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri)) + setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context)) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 219983038b..d1ad077fae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -634,6 +634,27 @@ class NotificationReceiver : BroadcastReceiver() { return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } + // KMK --> + /** + * Returns [PendingIntent] that opens the error log file in an external viewer + * + * @param context context of application + * @return [PendingIntent] + */ + internal fun openErrorLogPendingActivity(context: Context): PendingIntent { + val intent = Intent(context, MainActivity::class.java).apply { + action = Constants.SHORTCUT_LIBRARY_UPDATE_ERRORS + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + } + return PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + } + // KMK <-- + /** * Returns [PendingIntent] that cancels a backup restore job. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt index 59891011d4..42f60cd2cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt @@ -45,6 +45,7 @@ import eu.kanade.tachiyomi.ui.browse.BrowseTab import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen import eu.kanade.tachiyomi.ui.history.HistoryTab import eu.kanade.tachiyomi.ui.library.LibraryTab +import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.more.MoreTab import eu.kanade.tachiyomi.ui.updates.UpdatesTab @@ -188,8 +189,14 @@ object HomeScreen : Screen() { if (it is Tab.Library && it.mangaIdToOpen != null) { navigator.push(MangaScreen(it.mangaIdToOpen)) } - if (it is Tab.More && it.toDownloads) { - navigator.push(DownloadQueueScreen) + if (it is Tab.More) { + if (it.toDownloads) { + navigator.push(DownloadQueueScreen) + // KMK --> + } else if (it.toLibraryUpdateErrors) { + navigator.push(LibraryUpdateErrorScreen()) + // KMK <-- + } } } } @@ -339,6 +346,11 @@ object HomeScreen : Screen() { data object Updates : Tab data object History : Tab data class Browse(val toExtensions: Boolean = false) : Tab - data class More(val toDownloads: Boolean) : Tab + data class More( + val toDownloads: Boolean, + // KMK --> + val toLibraryUpdateErrors: Boolean = false, + // KMK <-- + ) : Tab } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreenModel.kt index fcb1a4335a..ef73b2062f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreenModel.kt @@ -159,6 +159,11 @@ data class LibraryUpdateErrorScreenState( } return uiModels } + + fun getHeaderIndexes(): List = getUiModel() + .withIndex() + .filter { it.value is LibraryUpdateErrorUiModel.Header } + .map { it.index } } @Immutable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 7b9e811ab1..2ef539aa28 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -544,6 +544,12 @@ class MainActivity : BaseActivity() { navigator.popUntilRoot() HomeScreen.Tab.More(toDownloads = true) } + // KMK --> + Constants.SHORTCUT_LIBRARY_UPDATE_ERRORS -> { + navigator.popUntilRoot() + HomeScreen.Tab.More(toDownloads = false, toLibraryUpdateErrors = true) + } + // KMK <-- Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> { // If the intent match the "standard" Android search intent // or the Google-specific search intent (triggered by saying or typing "search *query* on *Tachiyomi*" in Google Search/Google Assistant) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt index 8dcc72905f..bec74b6479 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt @@ -82,7 +82,6 @@ object MoreTab : Tab { onClickDownloadQueue = { navigator.push(DownloadQueueScreen) }, onClickCategories = { navigator.push(CategoryScreen()) }, onClickStats = { navigator.push(StatsScreen()) }, - onClickLibraryUpdateErrors = { navigator.push(LibraryUpdateErrorScreen()) }, onClickDataAndStorage = { navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage)) }, onClickSettings = { navigator.push(SettingsScreen()) }, onClickAbout = { navigator.push(SettingsScreen(SettingsScreen.Destination.About)) }, @@ -91,6 +90,9 @@ object MoreTab : Tab { onClickUpdates = { navigator.push(UpdatesTab) }, onClickHistory = { navigator.push(HistoryTab) }, // SY <-- + // KMK --> + onClickLibraryUpdateErrors = { navigator.push(LibraryUpdateErrorScreen()) }, + // KMK <-- ) } } diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/Constants.kt b/core/common/src/main/kotlin/tachiyomi/core/common/Constants.kt index 38caeb3dcb..7118b8cfdb 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/Constants.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/Constants.kt @@ -16,4 +16,8 @@ object Constants { const val SHORTCUT_SOURCES = "eu.kanade.tachiyomi.SHOW_CATALOGUES" const val SHORTCUT_EXTENSIONS = "eu.kanade.tachiyomi.EXTENSIONS" const val SHORTCUT_DOWNLOADS = "eu.kanade.tachiyomi.SHOW_DOWNLOADS" + + // KMK --> + const val SHORTCUT_LIBRARY_UPDATE_ERRORS = "eu.kanade.tachiyomi.SHOW_LIBRARY_UPDATE_ERRORS" + // KMK <-- } diff --git a/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorRepositoryImpl.kt index f90ce2c500..7fb014f7a3 100644 --- a/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorRepositoryImpl.kt @@ -37,6 +37,29 @@ class LibraryUpdateErrorRepositoryImpl( } } + override suspend fun deleteMangaError(mangaId: Long) { + return handler.await { + libraryUpdateErrorQueries.deleteMangaError( + mangaId = mangaId, + ) + } + } + + override suspend fun cleanUnrelevantMangaErrors() { + return handler.await { + libraryUpdateErrorQueries.cleanUnrelevantMangaErrors() + } + } + + override suspend fun upsert(libraryUpdateError: LibraryUpdateError) { + return handler.await(inTransaction = true) { + libraryUpdateErrorQueries.upsert( + mangaId = libraryUpdateError.mangaId, + messageId = libraryUpdateError.messageId, + ) + } + } + override suspend fun insert(libraryUpdateError: LibraryUpdateError) { return handler.await(inTransaction = true) { libraryUpdateErrorQueries.insert( diff --git a/data/src/main/java/tachiyomi/data/libraryUpdateErrorMessage/LibraryUpdateErrorMessageRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/libraryUpdateErrorMessage/LibraryUpdateErrorMessageRepositoryImpl.kt index 7094c0b37f..0a56050efc 100644 --- a/data/src/main/java/tachiyomi/data/libraryUpdateErrorMessage/LibraryUpdateErrorMessageRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/libraryUpdateErrorMessage/LibraryUpdateErrorMessageRepositoryImpl.kt @@ -29,14 +29,22 @@ class LibraryUpdateErrorMessageRepositoryImpl( return handler.await { libraryUpdateErrorMessageQueries.deleteAllErrorMessages() } } - override suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long? { - return handler.awaitOneOrNullExecutable(inTransaction = true) { + override suspend fun get(message: String): Long? { + return handler.awaitOneOrNullExecutable { + libraryUpdateErrorMessageQueries.getErrorMessages(message) { id, _ -> id } + } + } + + override suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long { + return handler.awaitOneExecutable(inTransaction = true) { libraryUpdateErrorMessageQueries.insert(libraryUpdateErrorMessage.message) libraryUpdateErrorMessageQueries.selectLastInsertedRowId() } } - override suspend fun insertAll(libraryUpdateErrorMessages: List): List> { + override suspend fun insertAll( + libraryUpdateErrorMessages: List, + ): List> { return handler.await(inTransaction = true) { libraryUpdateErrorMessages.map { libraryUpdateErrorMessageQueries.insert(it.message) diff --git a/data/src/main/sqldelight/tachiyomi/data/libraryUpdateError.sq b/data/src/main/sqldelight/tachiyomi/data/libraryUpdateError.sq index df03453af3..e5a5379109 100644 --- a/data/src/main/sqldelight/tachiyomi/data/libraryUpdateError.sq +++ b/data/src/main/sqldelight/tachiyomi/data/libraryUpdateError.sq @@ -1,6 +1,6 @@ CREATE TABLE libraryUpdateError ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - manga_id INTEGER NOT NULL, + manga_id INTEGER NOT NULL UNIQUE, message_id INTEGER NOT NULL ); @@ -11,9 +11,31 @@ FROM libraryUpdateError; insert: INSERT INTO libraryUpdateError(manga_id, message_id) VALUES (:mangaId, :messageId); +upsert: +INSERT INTO libraryUpdateError(manga_id, message_id) +VALUES (:mangaId, :messageId) +ON CONFLICT(manga_id) +DO UPDATE +SET + message_id = :messageId +WHERE manga_id = :mangaId; + deleteAllErrors: DELETE FROM libraryUpdateError; deleteError: DELETE FROM libraryUpdateError -WHERE _id = :_id; \ No newline at end of file +WHERE _id = :_id; + +deleteMangaError: +DELETE FROM libraryUpdateError +WHERE manga_id = :mangaId; + +cleanUnrelevantMangaErrors: +DELETE FROM libraryUpdateError +WHERE NOT EXISTS ( + SELECT 1 + FROM mangas + WHERE libraryUpdateError.manga_id = mangas._id + AND mangas.favorite == 1 +); \ No newline at end of file diff --git a/data/src/main/sqldelight/tachiyomi/data/libraryUpdateErrorMessage.sq b/data/src/main/sqldelight/tachiyomi/data/libraryUpdateErrorMessage.sq index 3ab788407a..85039cf829 100644 --- a/data/src/main/sqldelight/tachiyomi/data/libraryUpdateErrorMessage.sq +++ b/data/src/main/sqldelight/tachiyomi/data/libraryUpdateErrorMessage.sq @@ -7,6 +7,10 @@ getAllErrorMessages: SELECT * FROM libraryUpdateErrorMessage; +getErrorMessages: +SELECT * +FROM libraryUpdateErrorMessage WHERE message == :message; + insert: INSERT INTO libraryUpdateErrorMessage(message) VALUES (:message); diff --git a/data/src/main/sqldelight/tachiyomi/migrations/35.sqm b/data/src/main/sqldelight/tachiyomi/migrations/35.sqm index 2ae0d67e6a..c69dd8db32 100644 --- a/data/src/main/sqldelight/tachiyomi/migrations/35.sqm +++ b/data/src/main/sqldelight/tachiyomi/migrations/35.sqm @@ -2,7 +2,7 @@ DROP VIEW IF EXISTS libraryUpdateErrorView; CREATE TABLE IF NOT EXISTS libraryUpdateError ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - manga_id INTEGER NOT NULL, + manga_id INTEGER NOT NULL UNIQUE, message_id INTEGER NOT NULL ); diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/DeleteLibraryUpdateErrors.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/DeleteLibraryUpdateErrors.kt index 89dee977ec..f53158344c 100644 --- a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/DeleteLibraryUpdateErrors.kt +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/DeleteLibraryUpdateErrors.kt @@ -29,8 +29,28 @@ class DeleteLibraryUpdateErrors( } } + suspend fun deleteMangaError(mangaId: Long) = withNonCancellableContext { + try { + libraryUpdateErrorRepository.deleteMangaError(mangaId) + Result.Success + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + return@withNonCancellableContext Result.InternalError(e) + } + } + + suspend fun cleanUnrelevantMangaErrors() = withNonCancellableContext { + try { + libraryUpdateErrorRepository.cleanUnrelevantMangaErrors() + Result.Success + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + return@withNonCancellableContext Result.InternalError(e) + } + } + sealed class Result { - object Success : Result() + data object Success : Result() data class InternalError(val error: Throwable) : Result() } } diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/InsertLibraryUpdateErrors.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/InsertLibraryUpdateErrors.kt index 0e6aa536bd..0da60d61f4 100644 --- a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/InsertLibraryUpdateErrors.kt +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/InsertLibraryUpdateErrors.kt @@ -1,16 +1,42 @@ package tachiyomi.domain.libraryUpdateError.interactor +import logcat.LogPriority +import tachiyomi.core.common.util.lang.withNonCancellableContext +import tachiyomi.core.common.util.system.logcat +import tachiyomi.domain.libraryUpdateError.interactor.DeleteLibraryUpdateErrors.Result import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateError import tachiyomi.domain.libraryUpdateError.repository.LibraryUpdateErrorRepository class InsertLibraryUpdateErrors( private val libraryUpdateErrorRepository: LibraryUpdateErrorRepository, ) { - suspend fun insert(libraryUpdateError: LibraryUpdateError) { - return libraryUpdateErrorRepository.insert(libraryUpdateError) + suspend fun upsert(libraryUpdateError: LibraryUpdateError) = withNonCancellableContext { + try { + libraryUpdateErrorRepository.upsert(libraryUpdateError) + Result.Success + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + return@withNonCancellableContext Result.InternalError(e) + } } - suspend fun insertAll(libraryUpdateErrors: List) { - return libraryUpdateErrorRepository.insertAll(libraryUpdateErrors) + suspend fun insert(libraryUpdateError: LibraryUpdateError) = withNonCancellableContext { + try { + libraryUpdateErrorRepository.insert(libraryUpdateError) + Result.Success + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + return@withNonCancellableContext Result.InternalError(e) + } + } + + suspend fun insertAll(libraryUpdateErrors: List) = withNonCancellableContext { + try { + libraryUpdateErrorRepository.insertAll(libraryUpdateErrors) + Result.Success + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + return@withNonCancellableContext Result.InternalError(e) + } } } diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/repository/LibraryUpdateErrorRepository.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/repository/LibraryUpdateErrorRepository.kt index bb7ab0381e..b5a5889ce0 100644 --- a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/repository/LibraryUpdateErrorRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/repository/LibraryUpdateErrorRepository.kt @@ -13,6 +13,12 @@ interface LibraryUpdateErrorRepository { suspend fun delete(errorId: Long) + suspend fun deleteMangaError(mangaId: Long) + + suspend fun cleanUnrelevantMangaErrors() + + suspend fun upsert(libraryUpdateError: LibraryUpdateError) + suspend fun insert(libraryUpdateError: LibraryUpdateError) suspend fun insertAll(libraryUpdateErrors: List) diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/DeleteLibraryUpdateErrorMessages.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/DeleteLibraryUpdateErrorMessages.kt index 053c9b90f9..a790c50532 100644 --- a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/DeleteLibraryUpdateErrorMessages.kt +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/DeleteLibraryUpdateErrorMessages.kt @@ -21,7 +21,7 @@ class DeleteLibraryUpdateErrorMessages( } sealed class Result { - object Success : Result() + data object Success : Result() data class InternalError(val error: Throwable) : Result() } } diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/InsertLibraryUpdateErrorMessages.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/InsertLibraryUpdateErrorMessages.kt index a11695be8f..7152f719c1 100644 --- a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/InsertLibraryUpdateErrorMessages.kt +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/InsertLibraryUpdateErrorMessages.kt @@ -6,8 +6,11 @@ import tachiyomi.domain.libraryUpdateErrorMessage.repository.LibraryUpdateErrorM class InsertLibraryUpdateErrorMessages( private val libraryUpdateErrorMessageRepository: LibraryUpdateErrorMessageRepository, ) { + suspend fun get(message: String): Long? { + return libraryUpdateErrorMessageRepository.get(message) + } - suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long? { + suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long { return libraryUpdateErrorMessageRepository.insert(libraryUpdateErrorMessage) } diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/repository/LibraryUpdateErrorMessageRepository.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/repository/LibraryUpdateErrorMessageRepository.kt index d8272045eb..b427f9edbf 100644 --- a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/repository/LibraryUpdateErrorMessageRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/repository/LibraryUpdateErrorMessageRepository.kt @@ -11,7 +11,9 @@ interface LibraryUpdateErrorMessageRepository { suspend fun deleteAll() - suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long? + suspend fun get(message: String): Long? + + suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long suspend fun insertAll(libraryUpdateErrorMessages: List): List> } diff --git a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml index 5116f52532..b29efc2fa6 100644 --- a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml @@ -116,6 +116,10 @@ Library update errors Library update errors (%d) You have no library update errors. + Scroll to top + Scroll to bottom + Scroll to previous + Scroll to next Too many sources in your feed, cannot add more than limited (20) diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/ListGroupHeader.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/ListGroupHeader.kt index 1aaba373ed..3bfa45bab8 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/ListGroupHeader.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/ListGroupHeader.kt @@ -1,7 +1,9 @@ package tachiyomi.presentation.core.components +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -13,15 +15,23 @@ fun ListGroupHeader( text: String, modifier: Modifier = Modifier, ) { - Text( - text = text, + // KMK --> + Surface( modifier = modifier - .padding( - horizontal = MaterialTheme.padding.medium, - vertical = MaterialTheme.padding.small, - ), - color = MaterialTheme.colorScheme.onSurfaceVariant, - fontWeight = FontWeight.SemiBold, - style = MaterialTheme.typography.bodyMedium, - ) + .fillMaxWidth(), + color = MaterialTheme.colorScheme.surfaceContainerLow, + ) { + // KMK <-- + Text( + text = text, + modifier = Modifier + .padding( + horizontal = MaterialTheme.padding.medium, + vertical = MaterialTheme.padding.small, + ), + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.bodyMedium, + ) + } }