diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index 08787e1f8..02daac92e 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -37,6 +37,9 @@ 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 @@ -67,6 +70,16 @@ 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 @@ -191,5 +204,20 @@ class DomainModule : InjektModule { addFactory { DeleteExtensionRepo(get()) } addFactory { ReplaceExtensionRepo(get()) } addFactory { UpdateExtensionRepo(get(), 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()) } } } diff --git a/app/src/main/java/eu/kanade/presentation/libraryUpdateError/LibraryUpdateErrorScreen.kt b/app/src/main/java/eu/kanade/presentation/libraryUpdateError/LibraryUpdateErrorScreen.kt new file mode 100644 index 000000000..cff48cceb --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/libraryUpdateError/LibraryUpdateErrorScreen.kt @@ -0,0 +1,240 @@ +package eu.kanade.presentation.libraryUpdateError + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues +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.shape.ZeroCornerSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.FindReplace +import androidx.compose.material.icons.outlined.FlipToBack +import androidx.compose.material.icons.outlined.SelectAll +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.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +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.libraryUpdateError.components.libraryUpdateErrorUiItems +import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorItem +import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorScreenState +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import tachiyomi.i18n.MR +import tachiyomi.presentation.core.components.FastScrollLazyColumn +import tachiyomi.presentation.core.components.material.Scaffold +import tachiyomi.presentation.core.i18n.stringResource +import tachiyomi.presentation.core.screens.EmptyScreen +import tachiyomi.presentation.core.screens.LoadingScreen +import kotlin.time.Duration.Companion.seconds + +@Composable +fun LibraryUpdateErrorScreen( + state: LibraryUpdateErrorScreenState, + modifier: Modifier = Modifier, + onClick: (LibraryUpdateErrorItem) -> Unit, + onClickCover: (LibraryUpdateErrorItem) -> Unit, + onMultiMigrateClicked: (() -> Unit), + onSelectAll: (Boolean) -> Unit, + onInvertSelection: () -> Unit, + onErrorSelected: (LibraryUpdateErrorItem, Boolean, Boolean, Boolean) -> Unit, + navigateUp: () -> Unit, +) { + BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) }) + + Scaffold( + topBar = { scrollBehavior -> + LibraryUpdateErrorsAppBar( + title = stringResource( + MR.strings.label_library_update_errors, + state.items.size, + ), + actionModeCounter = state.selected.size, + onSelectAll = { onSelectAll(true) }, + onInvertSelection = onInvertSelection, + onCancelActionMode = { onSelectAll(false) }, + scrollBehavior = scrollBehavior, + navigateUp = navigateUp, + ) + }, + 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 + } + } + 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, + ) + } + } + } + }, + ) { paddingValues -> + when { + state.isLoading -> LoadingScreen(modifier = Modifier.padding(paddingValues)) + state.items.isEmpty() -> EmptyScreen( + message = stringResource(MR.strings.info_empty_library_update_errors), + modifier = Modifier.padding(paddingValues), + ) + + else -> { + FastScrollLazyColumn( + contentPadding = paddingValues, + ) { + libraryUpdateErrorUiItems( + uiModels = state.getUiModel(), + selectionMode = state.selectionMode, + onErrorSelected = onErrorSelected, + onClick = onClick, + onClickCover = onClickCover, + ) + } + } + } + } +} + +@Composable +private fun RowScope.Button( + title: String, + icon: ImageVector, + toConfirm: Boolean, + onLongClick: () -> Unit, + onClick: (() -> Unit), + content: (@Composable () -> Unit)? = null, +) { + val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f) + Column( + modifier = Modifier + .size(48.dp) + .weight(animatedWeight) + .combinedClickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(bounded = false), + onLongClick = onLongClick, + onClick = onClick, + ), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = icon, + contentDescription = title, + ) + AnimatedVisibility( + visible = toConfirm, + enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(), + exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(), + ) { + Text( + text = title, + overflow = TextOverflow.Visible, + maxLines = 1, + style = MaterialTheme.typography.labelSmall, + ) + } + content?.invoke() + } +} + +@Composable +private fun LibraryUpdateErrorsAppBar( + modifier: Modifier = Modifier, + title: String, + actionModeCounter: Int, + onSelectAll: () -> Unit, + onInvertSelection: () -> Unit, + onCancelActionMode: () -> Unit, + scrollBehavior: TopAppBarScrollBehavior, + navigateUp: () -> Unit, +) { + 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, + ) +} 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 new file mode 100644 index 000000000..dca14c78c --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/libraryUpdateError/components/LibraryUpdateErrorUiItem.kt @@ -0,0 +1,156 @@ +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.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 +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +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.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.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 + +internal fun LazyListScope.libraryUpdateErrorUiItems( + uiModels: List, + selectionMode: Boolean, + onErrorSelected: (LibraryUpdateErrorItem, Boolean, Boolean, Boolean) -> Unit, + 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) { + is LibraryUpdateErrorUiModel.Header -> { + ListGroupHeader( + modifier = Modifier.animateItemPlacement(), + text = item.errorMessage, + ) + } + + is LibraryUpdateErrorUiModel.Item -> { + val libraryUpdateErrorItem = item.item + LibraryUpdateErrorUiItem( + modifier = Modifier.animateItemPlacement(), + error = libraryUpdateErrorItem.error, + selected = libraryUpdateErrorItem.selected, + onClick = { + when { + selectionMode -> onErrorSelected( + libraryUpdateErrorItem, + !libraryUpdateErrorItem.selected, + true, + false, + ) + + else -> onClick(libraryUpdateErrorItem) + } + }, + onLongClick = { + onErrorSelected( + libraryUpdateErrorItem, + !libraryUpdateErrorItem.selected, + true, + true, + ) + }, + onClickCover = { onClickCover(libraryUpdateErrorItem) }.takeIf { !selectionMode }, + ) + } + } + } +} + +@Composable +private fun LibraryUpdateErrorUiItem( + modifier: Modifier, + error: LibraryUpdateErrorWithRelations, + selected: Boolean, + onClick: () -> Unit, + onLongClick: () -> Unit, + onClickCover: (() -> Unit)?, +) { + val haptic = LocalHapticFeedback.current + + Row( + modifier = modifier + .selectedBackground(selected) + .combinedClickable( + onClick = onClick, + onLongClick = { + onLongClick() + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + }, + ) + .padding(horizontal = MaterialTheme.padding.medium), + verticalAlignment = Alignment.Top, + ) { + MangaCover.Square( + modifier = Modifier + .padding(vertical = 6.dp) + .height(48.dp), + data = error.mangaCover, + onClick = onClickCover, + ) + + Column( + modifier = Modifier + .padding(horizontal = MaterialTheme.padding.medium, vertical = 5.dp) + .weight(1f), + ) { + Text( + text = error.mangaTitle, + style = MaterialTheme.typography.bodyMedium, + overflow = TextOverflow.Visible, + ) + + Row(modifier = Modifier.padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically) { + Text( + text = Injekt.get().getOrStub(error.mangaSource).name, + style = MaterialTheme.typography.bodySmall, + overflow = TextOverflow.Visible, + maxLines = 1, + modifier = Modifier + .secondaryItemAlpha() + .weight(weight = 1f, fill = false), + ) + } + } + } +} + +sealed class LibraryUpdateErrorUiModel { + + data class Header(val errorMessage: String) : LibraryUpdateErrorUiModel() + + data class Item(val item: LibraryUpdateErrorItem) : LibraryUpdateErrorUiModel() +} 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 fc690139a..20ef89b56 100644 --- a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.material.icons.automirrored.outlined.Label import androidx.compose.material.icons.outlined.CloudOff import androidx.compose.material.icons.outlined.GetApp import androidx.compose.material.icons.outlined.Info +import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material.icons.outlined.QueryStats import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Storage @@ -47,6 +48,7 @@ fun MoreScreen( onClickDataAndStorage: () -> Unit, onClickSettings: () -> Unit, onClickAbout: () -> Unit, + onClickLibraryUpdateErrors: () -> Unit, ) { val uriHandler = LocalUriHandler.current @@ -133,6 +135,13 @@ fun MoreScreen( onPreferenceClick = onClickStats, ) } + item { + TextPreferenceWidget( + title = stringResource(MR.strings.option_label_library_update_errors), + icon = Icons.Outlined.NewReleases, + onPreferenceClick = onClickLibraryUpdateErrors, + ) + } item { TextPreferenceWidget( title = stringResource(MR.strings.label_data_storage), 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 0982f4157..670aa875d 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 @@ -55,6 +55,12 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_U import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD +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 import tachiyomi.domain.manga.interactor.GetLibraryManga import tachiyomi.domain.manga.interactor.GetManga @@ -86,6 +92,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private val fetchInterval: FetchInterval = Injekt.get() private val filterChaptersForDownload: FilterChaptersForDownload = 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 notifier = LibraryUpdateNotifier(context) private var mangaToUpdate: List = mutableListOf() @@ -310,6 +321,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet } if (failedUpdates.isNotEmpty()) { + writeErrorsToDB(failedUpdates) val errorFile = writeErrorFile(failedUpdates) notifier.showUpdateErrorNotification( failedUpdates.size, @@ -406,6 +418,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet return File("") } + 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 -> + LibraryUpdateErrorMessage(-1L, errorMessage.orEmpty()) + }, + ) + val errorList = mutableListOf() + errorMessages.forEach { + libraryErrors[it.second]?.forEach { manga -> + errorList.add(LibraryUpdateError(id = -1L, mangaId = manga.id, messageId = it.first)) + } + } + insertLibraryUpdateErrors.insertAll(errorList) + } + companion object { private const val TAG = "LibraryUpdate" private const val WORK_NAME_AUTO = "LibraryUpdate-auto" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreen.kt new file mode 100644 index 000000000..f49bcf21c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreen.kt @@ -0,0 +1,48 @@ +package eu.kanade.tachiyomi.ui.libraryUpdateError + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.libraryUpdateError.LibraryUpdateErrorScreen +import eu.kanade.presentation.util.Screen +import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen +import eu.kanade.tachiyomi.ui.manga.MangaScreen +import tachiyomi.domain.UnsortedPreferences +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class LibraryUpdateErrorScreen : Screen() { + + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + val screenModel = rememberScreenModel { LibraryUpdateErrorScreenModel() } + val state by screenModel.state.collectAsState() + + LibraryUpdateErrorScreen( + state = state, + onClick = { item -> + PreMigrationScreen.navigateToMigration( + Injekt.get().skipPreMigration().get(), + navigator, + listOf(item.error.mangaId), + ) + }, + onClickCover = { item -> navigator.push(MangaScreen(item.error.mangaId)) }, + onMultiMigrateClicked = { + PreMigrationScreen.navigateToMigration( + Injekt.get().skipPreMigration().get(), + navigator, + state.selected.map { it.error.mangaId }, + ) + }, + onSelectAll = screenModel::toggleAllSelection, + onInvertSelection = screenModel::invertSelection, + onErrorSelected = screenModel::toggleSelection, + navigateUp = navigator::pop, + ) + } +} 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 new file mode 100644 index 000000000..fcb1a4335 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/libraryUpdateError/LibraryUpdateErrorScreenModel.kt @@ -0,0 +1,168 @@ +package eu.kanade.tachiyomi.ui.libraryUpdateError + +import androidx.compose.runtime.Immutable +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import eu.kanade.core.util.addOrRemove +import eu.kanade.presentation.libraryUpdateError.components.LibraryUpdateErrorUiModel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.update +import tachiyomi.core.common.util.lang.launchIO +import tachiyomi.domain.libraryUpdateError.interactor.GetLibraryUpdateErrorWithRelations +import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations +import tachiyomi.domain.libraryUpdateErrorMessage.interactor.GetLibraryUpdateErrorMessages +import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class LibraryUpdateErrorScreenModel( + private val getLibraryUpdateErrorWithRelations: GetLibraryUpdateErrorWithRelations = Injekt.get(), + private val getLibraryUpdateErrorMessages: GetLibraryUpdateErrorMessages = Injekt.get(), +) : StateScreenModel(LibraryUpdateErrorScreenState()) { + + // First and last selected index in list + private val selectedPositions: Array = arrayOf(-1, -1) + private val selectedErrorIds: HashSet = HashSet() + + init { + screenModelScope.launchIO { + getLibraryUpdateErrorWithRelations.subscribeAll() + .collectLatest { errors -> + val errorMessages = getLibraryUpdateErrorMessages.await() + mutableState.update { + it.copy( + isLoading = false, + items = toLibraryUpdateErrorItems(errors), + messages = errorMessages, + ) + } + } + } + } + + private fun toLibraryUpdateErrorItems(errors: List): List { + return errors.map { error -> + LibraryUpdateErrorItem( + error = error, + selected = error.errorId in selectedErrorIds, + ) + } + } + + fun toggleSelection( + item: LibraryUpdateErrorItem, + selected: Boolean, + userSelected: Boolean = false, + fromLongPress: Boolean = false, + ) { + mutableState.update { state -> + val newItems = state.items.toMutableList().apply { + val selectedIndex = indexOfFirst { it.error.errorId == item.error.errorId } + if (selectedIndex < 0) return@apply + + val selectedItem = get(selectedIndex) + if (selectedItem.selected == selected) return@apply + + val firstSelection = none { it.selected } + set(selectedIndex, selectedItem.copy(selected = selected)) + selectedErrorIds.addOrRemove(item.error.errorId, selected) + + if (selected && userSelected && fromLongPress) { + if (firstSelection) { + selectedPositions[0] = selectedIndex + selectedPositions[1] = selectedIndex + } else { + // Try to select the items in-between when possible + val range: IntRange + if (selectedIndex < selectedPositions[0]) { + range = selectedIndex + 1 until selectedPositions[0] + selectedPositions[0] = selectedIndex + } else if (selectedIndex > selectedPositions[1]) { + range = (selectedPositions[1] + 1) until selectedIndex + selectedPositions[1] = selectedIndex + } else { + // Just select itself + range = IntRange.EMPTY + } + + range.forEach { + val inbetweenItem = get(it) + if (!inbetweenItem.selected) { + selectedErrorIds.add(inbetweenItem.error.errorId) + set(it, inbetweenItem.copy(selected = true)) + } + } + } + } else if (userSelected && !fromLongPress) { + if (!selected) { + if (selectedIndex == selectedPositions[0]) { + selectedPositions[0] = indexOfFirst { it.selected } + } else if (selectedIndex == selectedPositions[1]) { + selectedPositions[1] = indexOfLast { it.selected } + } + } else { + if (selectedIndex < selectedPositions[0]) { + selectedPositions[0] = selectedIndex + } else if (selectedIndex > selectedPositions[1]) { + selectedPositions[1] = selectedIndex + } + } + } + } + state.copy(items = newItems) + } + } + + fun toggleAllSelection(selected: Boolean) { + mutableState.update { state -> + val newItems = state.items.map { + selectedErrorIds.addOrRemove(it.error.errorId, selected) + it.copy(selected = selected) + } + state.copy(items = newItems) + } + + selectedPositions[0] = -1 + selectedPositions[1] = -1 + } + + fun invertSelection() { + mutableState.update { state -> + val newItems = state.items.map { + selectedErrorIds.addOrRemove(it.error.errorId, !it.selected) + it.copy(selected = !it.selected) + } + state.copy(items = newItems) + } + selectedPositions[0] = -1 + selectedPositions[1] = -1 + } +} + +@Immutable +data class LibraryUpdateErrorScreenState( + val isLoading: Boolean = true, + val items: List = emptyList(), + val messages: List = emptyList(), +) { + + val selected = items.filter { it.selected } + val selectionMode = selected.isNotEmpty() + + fun getUiModel(): List { + val uiModels = mutableListOf() + val errorMap = items.groupBy { it.error.messageId } + errorMap.forEach { (messageId, errors) -> + val message = messages.find { it.id == messageId } + uiModels.add(LibraryUpdateErrorUiModel.Header(message!!.message)) + uiModels.addAll(errors.map { LibraryUpdateErrorUiModel.Item(it) }) + } + return uiModels + } +} + +@Immutable +data class LibraryUpdateErrorItem( + val error: LibraryUpdateErrorWithRelations, + val selected: Boolean, +) 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 0279a6975..134b16ced 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 @@ -24,6 +24,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen +import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorScreen import eu.kanade.tachiyomi.ui.setting.SettingsScreen import eu.kanade.tachiyomi.ui.stats.StatsScreen import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid @@ -75,6 +76,7 @@ object MoreTab : Tab { onClickDataAndStorage = { navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage)) }, onClickSettings = { navigator.push(SettingsScreen()) }, onClickAbout = { navigator.push(SettingsScreen(SettingsScreen.Destination.About)) }, + onClickLibraryUpdateErrors = { navigator.push(LibraryUpdateErrorScreen()) }, ) } } diff --git a/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorMapper.kt b/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorMapper.kt new file mode 100644 index 000000000..6a6e9d123 --- /dev/null +++ b/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorMapper.kt @@ -0,0 +1,11 @@ +package tachiyomi.data.libraryUpdateError + +import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateError + +val libraryUpdateErrorMapper: (Long, Long, Long) -> LibraryUpdateError = { id, mangaId, messageId -> + LibraryUpdateError( + id = id, + mangaId = mangaId, + messageId = messageId, + ) +} diff --git a/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorRepositoryImpl.kt new file mode 100644 index 000000000..f90ce2c50 --- /dev/null +++ b/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorRepositoryImpl.kt @@ -0,0 +1,59 @@ +package tachiyomi.data.libraryUpdateError + +import kotlinx.coroutines.flow.Flow +import tachiyomi.data.DatabaseHandler +import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateError +import tachiyomi.domain.libraryUpdateError.repository.LibraryUpdateErrorRepository + +class LibraryUpdateErrorRepositoryImpl( + private val handler: DatabaseHandler, +) : LibraryUpdateErrorRepository { + + override suspend fun getAll(): List { + return handler.awaitList { + libraryUpdateErrorQueries.getAllErrors( + libraryUpdateErrorMapper, + ) + } + } + + override fun getAllAsFlow(): Flow> { + return handler.subscribeToList { + libraryUpdateErrorQueries.getAllErrors( + libraryUpdateErrorMapper, + ) + } + } + + override suspend fun deleteAll() { + return handler.await { libraryUpdateErrorQueries.deleteAllErrors() } + } + + override suspend fun delete(errorId: Long) { + return handler.await { + libraryUpdateErrorQueries.deleteError( + _id = errorId, + ) + } + } + + override suspend fun insert(libraryUpdateError: LibraryUpdateError) { + return handler.await(inTransaction = true) { + libraryUpdateErrorQueries.insert( + mangaId = libraryUpdateError.mangaId, + messageId = libraryUpdateError.messageId, + ) + } + } + + override suspend fun insertAll(libraryUpdateErrors: List) { + return handler.await(inTransaction = true) { + libraryUpdateErrors.forEach { + libraryUpdateErrorQueries.insert( + mangaId = it.mangaId, + messageId = it.messageId, + ) + } + } + } +} diff --git a/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorWithRelationsMapper.kt b/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorWithRelationsMapper.kt new file mode 100644 index 000000000..bed47b440 --- /dev/null +++ b/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorWithRelationsMapper.kt @@ -0,0 +1,23 @@ +package tachiyomi.data.libraryUpdateError + +import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations +import tachiyomi.domain.manga.model.MangaCover + +val libraryUpdateErrorWithRelationsMapper: + (Long, String, Long, Boolean, String?, Long, Long, Long) -> LibraryUpdateErrorWithRelations = + { mangaId, mangaTitle, mangaSource, favorite, mangaThumbnail, coverLastModified, errorId, messageId -> + LibraryUpdateErrorWithRelations( + mangaId = mangaId, + mangaTitle = mangaTitle, + mangaSource = mangaSource, + mangaCover = MangaCover( + mangaId = mangaId, + sourceId = mangaSource, + isMangaFavorite = favorite, + url = mangaThumbnail, + lastModified = coverLastModified, + ), + errorId = errorId, + messageId = messageId, + ) + } diff --git a/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorWithRelationsRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorWithRelationsRepositoryImpl.kt new file mode 100644 index 000000000..d5b31da3f --- /dev/null +++ b/data/src/main/java/tachiyomi/data/libraryUpdateError/LibraryUpdateErrorWithRelationsRepositoryImpl.kt @@ -0,0 +1,19 @@ +package tachiyomi.data.libraryUpdateError + +import kotlinx.coroutines.flow.Flow +import tachiyomi.data.DatabaseHandler +import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations +import tachiyomi.domain.libraryUpdateError.repository.LibraryUpdateErrorWithRelationsRepository + +class LibraryUpdateErrorWithRelationsRepositoryImpl( + private val handler: DatabaseHandler, +) : LibraryUpdateErrorWithRelationsRepository { + + override fun subscribeAll(): Flow> { + return handler.subscribeToList { + libraryUpdateErrorViewQueries.errors( + libraryUpdateErrorWithRelationsMapper, + ) + } + } +} diff --git a/data/src/main/java/tachiyomi/data/libraryUpdateErrorMessage/LibraryUpdateErrorMessageMapper.kt b/data/src/main/java/tachiyomi/data/libraryUpdateErrorMessage/LibraryUpdateErrorMessageMapper.kt new file mode 100644 index 000000000..941a7ec08 --- /dev/null +++ b/data/src/main/java/tachiyomi/data/libraryUpdateErrorMessage/LibraryUpdateErrorMessageMapper.kt @@ -0,0 +1,10 @@ +package tachiyomi.data.libraryUpdateErrorMessage + +import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage + +val LibraryUpdateErrorMessageMapper: (Long, String) -> LibraryUpdateErrorMessage = { id, message -> + LibraryUpdateErrorMessage( + id = id, + message = message, + ) +} diff --git a/data/src/main/java/tachiyomi/data/libraryUpdateErrorMessage/LibraryUpdateErrorMessageRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/libraryUpdateErrorMessage/LibraryUpdateErrorMessageRepositoryImpl.kt new file mode 100644 index 000000000..7094c0b37 --- /dev/null +++ b/data/src/main/java/tachiyomi/data/libraryUpdateErrorMessage/LibraryUpdateErrorMessageRepositoryImpl.kt @@ -0,0 +1,47 @@ +package tachiyomi.data.libraryUpdateErrorMessage + +import kotlinx.coroutines.flow.Flow +import tachiyomi.data.DatabaseHandler +import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage +import tachiyomi.domain.libraryUpdateErrorMessage.repository.LibraryUpdateErrorMessageRepository + +class LibraryUpdateErrorMessageRepositoryImpl( + private val handler: DatabaseHandler, +) : LibraryUpdateErrorMessageRepository { + + override suspend fun getAll(): List { + return handler.awaitList { + libraryUpdateErrorMessageQueries.getAllErrorMessages( + LibraryUpdateErrorMessageMapper, + ) + } + } + + override fun getAllAsFlow(): Flow> { + return handler.subscribeToList { + libraryUpdateErrorMessageQueries.getAllErrorMessages( + LibraryUpdateErrorMessageMapper, + ) + } + } + + override suspend fun deleteAll() { + return handler.await { libraryUpdateErrorMessageQueries.deleteAllErrorMessages() } + } + + override suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long? { + return handler.awaitOneOrNullExecutable(inTransaction = true) { + libraryUpdateErrorMessageQueries.insert(libraryUpdateErrorMessage.message) + libraryUpdateErrorMessageQueries.selectLastInsertedRowId() + } + } + + override suspend fun insertAll(libraryUpdateErrorMessages: List): List> { + return handler.await(inTransaction = true) { + libraryUpdateErrorMessages.map { + libraryUpdateErrorMessageQueries.insert(it.message) + libraryUpdateErrorMessageQueries.selectLastInsertedRowId().executeAsOne() to it.message + } + } + } +} diff --git a/data/src/main/sqldelight/tachiyomi/data/libraryUpdateError.sq b/data/src/main/sqldelight/tachiyomi/data/libraryUpdateError.sq new file mode 100644 index 000000000..df03453af --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/data/libraryUpdateError.sq @@ -0,0 +1,19 @@ +CREATE TABLE libraryUpdateError ( + _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + manga_id INTEGER NOT NULL, + message_id INTEGER NOT NULL +); + +getAllErrors: +SELECT * +FROM libraryUpdateError; + +insert: +INSERT INTO libraryUpdateError(manga_id, message_id) VALUES (:mangaId, :messageId); + +deleteAllErrors: +DELETE FROM libraryUpdateError; + +deleteError: +DELETE FROM libraryUpdateError +WHERE _id = :_id; \ 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 new file mode 100644 index 000000000..3ab788407 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/data/libraryUpdateErrorMessage.sq @@ -0,0 +1,17 @@ +CREATE TABLE libraryUpdateErrorMessage ( + _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + message TEXT NOT NULL UNIQUE +); + +getAllErrorMessages: +SELECT * +FROM libraryUpdateErrorMessage; + +insert: +INSERT INTO libraryUpdateErrorMessage(message) VALUES (:message); + +deleteAllErrorMessages: +DELETE FROM libraryUpdateErrorMessage; + +selectLastInsertedRowId: +SELECT last_insert_rowid(); \ No newline at end of file diff --git a/data/src/main/sqldelight/tachiyomi/migrations/4.sqm b/data/src/main/sqldelight/tachiyomi/migrations/4.sqm new file mode 100644 index 000000000..2ae0d67e6 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/migrations/4.sqm @@ -0,0 +1,26 @@ +DROP VIEW IF EXISTS libraryUpdateErrorView; + +CREATE TABLE IF NOT EXISTS libraryUpdateError ( + _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + manga_id INTEGER NOT NULL, + message_id INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS libraryUpdateErrorMessage ( + _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + message TEXT NOT NULL UNIQUE +); + +CREATE VIEW libraryUpdateErrorView AS +SELECT + mangas._id AS mangaId, + mangas.title AS mangaTitle, + mangas.source, + mangas.favorite, + mangas.thumbnail_url AS thumbnailUrl, + mangas.cover_last_modified AS coverLastModified, + libraryUpdateError._id AS errorId, + libraryUpdateError.message_id AS messageId +FROM mangas JOIN libraryUpdateError +ON mangas._id = libraryUpdateError.manga_id +WHERE favorite = 1; \ No newline at end of file diff --git a/data/src/main/sqldelight/tachiyomi/view/libraryUpdateErrorView.sq b/data/src/main/sqldelight/tachiyomi/view/libraryUpdateErrorView.sq new file mode 100644 index 000000000..303b95eaf --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/view/libraryUpdateErrorView.sq @@ -0,0 +1,17 @@ +CREATE VIEW libraryUpdateErrorView AS +SELECT + mangas._id AS mangaId, + mangas.title AS mangaTitle, + mangas.source, + mangas.favorite, + mangas.thumbnail_url AS thumbnailUrl, + mangas.cover_last_modified AS coverLastModified, + libraryUpdateError._id AS errorId, + libraryUpdateError.message_id AS messageId +FROM mangas JOIN libraryUpdateError +ON mangas._id = libraryUpdateError.manga_id +WHERE favorite = 1; + +errors: +SELECT * +FROM libraryUpdateErrorView; \ No newline at end of file diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/DeleteLibraryUpdateErrors.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/DeleteLibraryUpdateErrors.kt new file mode 100644 index 000000000..89dee977e --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/DeleteLibraryUpdateErrors.kt @@ -0,0 +1,36 @@ +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.repository.LibraryUpdateErrorRepository + +class DeleteLibraryUpdateErrors( + private val libraryUpdateErrorRepository: LibraryUpdateErrorRepository, +) { + + suspend fun await() = withNonCancellableContext { + try { + libraryUpdateErrorRepository.deleteAll() + Result.Success + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + return@withNonCancellableContext Result.InternalError(e) + } + } + + suspend fun await(errorId: Long) = withNonCancellableContext { + try { + libraryUpdateErrorRepository.delete(errorId) + Result.Success + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + return@withNonCancellableContext Result.InternalError(e) + } + } + + sealed class Result { + object Success : Result() + data class InternalError(val error: Throwable) : Result() + } +} diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/GetLibraryUpdateErrorWithRelations.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/GetLibraryUpdateErrorWithRelations.kt new file mode 100644 index 000000000..58640243e --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/GetLibraryUpdateErrorWithRelations.kt @@ -0,0 +1,14 @@ +package tachiyomi.domain.libraryUpdateError.interactor + +import kotlinx.coroutines.flow.Flow +import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations +import tachiyomi.domain.libraryUpdateError.repository.LibraryUpdateErrorWithRelationsRepository + +class GetLibraryUpdateErrorWithRelations( + private val libraryUpdateErrorWithRelationsRepository: LibraryUpdateErrorWithRelationsRepository, +) { + + fun subscribeAll(): Flow> { + return libraryUpdateErrorWithRelationsRepository.subscribeAll() + } +} diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/GetLibraryUpdateErrors.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/GetLibraryUpdateErrors.kt new file mode 100644 index 000000000..b8fd56bba --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/GetLibraryUpdateErrors.kt @@ -0,0 +1,18 @@ +package tachiyomi.domain.libraryUpdateError.interactor + +import kotlinx.coroutines.flow.Flow +import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateError +import tachiyomi.domain.libraryUpdateError.repository.LibraryUpdateErrorRepository + +class GetLibraryUpdateErrors( + private val libraryUpdateErrorRepository: LibraryUpdateErrorRepository, +) { + + fun subscribe(): Flow> { + return libraryUpdateErrorRepository.getAllAsFlow() + } + + suspend fun await(): List { + return libraryUpdateErrorRepository.getAll() + } +} diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/InsertLibraryUpdateErrors.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/InsertLibraryUpdateErrors.kt new file mode 100644 index 000000000..0e6aa536b --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/interactor/InsertLibraryUpdateErrors.kt @@ -0,0 +1,16 @@ +package tachiyomi.domain.libraryUpdateError.interactor + +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 insertAll(libraryUpdateErrors: List) { + return libraryUpdateErrorRepository.insertAll(libraryUpdateErrors) + } +} diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/model/LibraryUpdateError.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/model/LibraryUpdateError.kt new file mode 100644 index 000000000..7838d378a --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/model/LibraryUpdateError.kt @@ -0,0 +1,9 @@ +package tachiyomi.domain.libraryUpdateError.model + +import java.io.Serializable + +data class LibraryUpdateError( + val id: Long, + val mangaId: Long, + val messageId: Long, +) : Serializable diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/model/LibraryUpdateErrorWithRelations.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/model/LibraryUpdateErrorWithRelations.kt new file mode 100644 index 000000000..f2c514cbc --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/model/LibraryUpdateErrorWithRelations.kt @@ -0,0 +1,12 @@ +package tachiyomi.domain.libraryUpdateError.model + +import tachiyomi.domain.manga.model.MangaCover + +data class LibraryUpdateErrorWithRelations( + val mangaId: Long, + val mangaTitle: String, + val mangaSource: Long, + val mangaCover: MangaCover, + val errorId: Long, + val messageId: Long, +) diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/repository/LibraryUpdateErrorRepository.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/repository/LibraryUpdateErrorRepository.kt new file mode 100644 index 000000000..bb7ab0381 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/repository/LibraryUpdateErrorRepository.kt @@ -0,0 +1,19 @@ +package tachiyomi.domain.libraryUpdateError.repository + +import kotlinx.coroutines.flow.Flow +import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateError + +interface LibraryUpdateErrorRepository { + + suspend fun getAll(): List + + fun getAllAsFlow(): Flow> + + suspend fun deleteAll() + + suspend fun delete(errorId: Long) + + suspend fun insert(libraryUpdateError: LibraryUpdateError) + + suspend fun insertAll(libraryUpdateErrors: List) +} diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateError/repository/LibraryUpdateErrorWithRelationsRepository.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/repository/LibraryUpdateErrorWithRelationsRepository.kt new file mode 100644 index 000000000..460334d48 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateError/repository/LibraryUpdateErrorWithRelationsRepository.kt @@ -0,0 +1,9 @@ +package tachiyomi.domain.libraryUpdateError.repository + +import kotlinx.coroutines.flow.Flow +import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateErrorWithRelations + +interface LibraryUpdateErrorWithRelationsRepository { + + fun subscribeAll(): Flow> +} diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/DeleteLibraryUpdateErrorMessages.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/DeleteLibraryUpdateErrorMessages.kt new file mode 100644 index 000000000..053c9b90f --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/DeleteLibraryUpdateErrorMessages.kt @@ -0,0 +1,27 @@ +package tachiyomi.domain.libraryUpdateErrorMessage.interactor + +import logcat.LogPriority +import tachiyomi.core.common.util.lang.withNonCancellableContext +import tachiyomi.core.common.util.system.logcat +import tachiyomi.domain.libraryUpdateErrorMessage.repository.LibraryUpdateErrorMessageRepository +import kotlin.Exception + +class DeleteLibraryUpdateErrorMessages( + private val libraryUpdateErrorMessageRepository: LibraryUpdateErrorMessageRepository, +) { + + suspend fun await() = withNonCancellableContext { + try { + libraryUpdateErrorMessageRepository.deleteAll() + Result.Success + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + return@withNonCancellableContext Result.InternalError(e) + } + } + + sealed class Result { + object Success : Result() + data class InternalError(val error: Throwable) : Result() + } +} diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/GetLibraryUpdateErrorMessages.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/GetLibraryUpdateErrorMessages.kt new file mode 100644 index 000000000..778ac4cb1 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/GetLibraryUpdateErrorMessages.kt @@ -0,0 +1,18 @@ +package tachiyomi.domain.libraryUpdateErrorMessage.interactor + +import kotlinx.coroutines.flow.Flow +import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage +import tachiyomi.domain.libraryUpdateErrorMessage.repository.LibraryUpdateErrorMessageRepository + +class GetLibraryUpdateErrorMessages( + private val libraryUpdateErrorMessageRepository: LibraryUpdateErrorMessageRepository, +) { + + fun subscribe(): Flow> { + return libraryUpdateErrorMessageRepository.getAllAsFlow() + } + + suspend fun await(): List { + return libraryUpdateErrorMessageRepository.getAll() + } +} diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/InsertLibraryUpdateErrorMessages.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/InsertLibraryUpdateErrorMessages.kt new file mode 100644 index 000000000..a11695be8 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/interactor/InsertLibraryUpdateErrorMessages.kt @@ -0,0 +1,17 @@ +package tachiyomi.domain.libraryUpdateErrorMessage.interactor + +import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage +import tachiyomi.domain.libraryUpdateErrorMessage.repository.LibraryUpdateErrorMessageRepository + +class InsertLibraryUpdateErrorMessages( + private val libraryUpdateErrorMessageRepository: LibraryUpdateErrorMessageRepository, +) { + + suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long? { + return libraryUpdateErrorMessageRepository.insert(libraryUpdateErrorMessage) + } + + suspend fun insertAll(libraryUpdateErrorMessages: List): List> { + return libraryUpdateErrorMessageRepository.insertAll(libraryUpdateErrorMessages) + } +} diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/model/LibraryUpdateErrorMessage.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/model/LibraryUpdateErrorMessage.kt new file mode 100644 index 000000000..072807483 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/model/LibraryUpdateErrorMessage.kt @@ -0,0 +1,8 @@ +package tachiyomi.domain.libraryUpdateErrorMessage.model + +import java.io.Serializable + +data class LibraryUpdateErrorMessage( + val id: Long, + val message: String, +) : Serializable diff --git a/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/repository/LibraryUpdateErrorMessageRepository.kt b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/repository/LibraryUpdateErrorMessageRepository.kt new file mode 100644 index 000000000..d8272045e --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/libraryUpdateErrorMessage/repository/LibraryUpdateErrorMessageRepository.kt @@ -0,0 +1,17 @@ +package tachiyomi.domain.libraryUpdateErrorMessage.repository + +import kotlinx.coroutines.flow.Flow +import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage + +interface LibraryUpdateErrorMessageRepository { + + suspend fun getAll(): List + + fun getAllAsFlow(): Flow> + + suspend fun deleteAll() + + suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long? + + suspend fun insertAll(libraryUpdateErrorMessages: List): List> +} diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index d6020896a..216feaf71 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -573,6 +573,11 @@ Syncing library Library sync complete + + Library update errors + Library update errors (%d) + You have no library update errors. + Networking Clear cookies