diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt index 4f83249666..36e575424a 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt @@ -358,14 +358,14 @@ fun FailedUpdatesBottomActionMenu( .padding(horizontal = 8.dp, vertical = 12.dp), ) { Button( - title = stringResource(R.string.action_delete), + title = stringResource(MR.strings.action_delete), icon = Icons.Outlined.Delete, toConfirm = confirm[0], onLongClick = { onLongClickItem(0) }, onClick = onDeleteClicked, ) Button( - title = stringResource(R.string.action_dismiss), + title = stringResource(MR.strings.action_dismiss), icon = Icons.Outlined.VisibilityOff, toConfirm = confirm[1], onLongClick = { onLongClickItem(1) }, @@ -373,7 +373,7 @@ fun FailedUpdatesBottomActionMenu( ) if (groupingMode == GroupByMode.NONE && selected.size <= 1) { Button( - title = stringResource(R.string.action_info), + title = stringResource(MR.strings.action_info), icon = Icons.Outlined.Info, toConfirm = confirm[2], onLongClick = { onLongClickItem(2) }, diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt index 9ea5a2d1cf..1c9a814de3 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt @@ -30,6 +30,7 @@ import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.ui.updates.UpdatesItem import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.launch import tachiyomi.i18n.MR @@ -152,26 +153,33 @@ private fun UpdatesAppBar( modifier = modifier, title = stringResource(MR.strings.label_recent_updates), actions = { - val actions = mutableListOf() - if (hasFailedUpdates) { // only add the warning icon if it is enabled - actions += AppBar.Action( - title = stringResource(R.string.action_update_warning), - icon = Icons.Rounded.Warning, - onClick = onUpdateWarning, - iconTint = warningIconTint, + val actions = mutableListOf().apply { + if (hasFailedUpdates) { + add( + AppBar.Action( + title = stringResource(MR.strings.action_update_warning), + icon = Icons.Rounded.Warning, + onClick = onUpdateWarning, + iconTint = warningIconTint, + ), + ) + } + add( + AppBar.Action( + title = stringResource(MR.strings.action_view_upcoming), + icon = Icons.Outlined.CalendarMonth, + onClick = onCalendarClicked, + ), + ) + add( + AppBar.Action( + title = stringResource(MR.strings.action_update_library), + icon = Icons.Outlined.Refresh, + onClick = onUpdateLibrary, + ), ) } - actions += AppBar.Action( - title = stringResource(MR.strings.action_view_upcoming), - icon = Icons.Outlined.CalendarMonth, - onClick = onCalendarClicked, - ) - actions += AppBar.Action( - title = stringResource(R.string.action_update_library), - icon = Icons.Outlined.Refresh, - onClick = onUpdateLibrary, - ) - AppBarActions(actions) + AppBarActions(actions.toImmutableList()) }, actionModeCounter = actionModeCounter, onCancelActionMode = onCancelActionMode, diff --git a/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesContent.kt b/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesContent.kt index be4350e5f3..f2d1eb508d 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesContent.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesContent.kt @@ -27,9 +27,9 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Dangerous import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.rounded.Warning -import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.CardDefaults import androidx.compose.material3.ElevatedCard import androidx.compose.material3.HorizontalDivider @@ -38,6 +38,7 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.minimumInteractiveComponentSize +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -49,6 +50,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.hapticfeedback.HapticFeedbackType @@ -244,6 +246,15 @@ fun LazyListScope.failedUpdatesGroupUiItem( .height(50.dp) .aspectRatio(1f), ) + } else { + Image( + imageVector = Icons.Filled.Dangerous, + contentDescription = null, + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error), + modifier = Modifier + .height(50.dp) + .aspectRatio(1f) + ) } Column( modifier = Modifier @@ -313,11 +324,13 @@ fun LazyListScope.failedUpdatesGroupUiItem( if (expanded[errorMessageHeaderId] == null) { onExpandedMapChange(errorMessageHeaderId, true) } else { - onExpandedMapChange(errorMessageHeaderId, !expanded[errorMessageHeaderId]!!) + onExpandedMapChange( + errorMessageHeaderId, + !expanded[errorMessageHeaderId]!!, + ) } }, - onLongClick = - { onGroupSelected(items) }, + onLongClick = { onGroupSelected(items) }, ) .padding( horizontal = 12.dp, @@ -390,7 +403,9 @@ fun LazyListScope.failedUpdatesGroupUiItem( val isLastItem = index == items.lastIndex AnimatedVisibility( modifier = Modifier, - visible = expanded[errorMessageHeaderId] == true && expanded[GroupKey(id, Pair("", ""))] == true, + visible = + expanded[errorMessageHeaderId] == true && + expanded[GroupKey(id, Pair("", ""))] == true, ) { FailedUpdatesUiItem( modifier = Modifier @@ -443,10 +458,12 @@ fun CustomIconButton( enabled = enabled, role = Role.Button, interactionSource = interactionSource, - indication = rememberRipple( - bounded = false, - radius = 20.dp, - ), + indication = remember { + ripple( + bounded = false, + radius = 20.dp, + ) + }, ), contentAlignment = Alignment.Center, ) { diff --git a/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesDialog.kt b/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesDialog.kt index 9711aa0252..5a74d9f9b5 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesDialog.kt @@ -32,7 +32,7 @@ fun ErrorMessageDialog( TextButton(onClick = { onCopyClick() onDismissRequest() - },) { + }) { Text(text = stringResource(R.string.copy)) } }, diff --git a/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesScreen.kt index de96e8e1f1..0bad7d75ad 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesScreen.kt @@ -51,7 +51,9 @@ import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.util.system.copyToClipboard +import kotlinx.collections.immutable.toImmutableList import tachiyomi.domain.manga.model.Manga +import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.FastScrollLazyColumn import tachiyomi.presentation.core.components.Pill import tachiyomi.presentation.core.components.SortItem @@ -59,8 +61,6 @@ import tachiyomi.presentation.core.components.material.ExtendedFloatingActionBut import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.LoadingScreen -import tachiyomi.presentation.core.util.isScrolledToEnd -import tachiyomi.presentation.core.util.isScrollingUp import tachiyomi.source.local.isLocal class FailedUpdatesScreen : Screen() { @@ -120,7 +120,8 @@ class FailedUpdatesScreen : Screen() { text = { Text(text = stringResource(R.string.label_help)) }, icon = { Icon(imageVector = Icons.Outlined.HelpOutline, contentDescription = null) }, onClick = { uriHandler.openUri("https://tachiyomi.org/help/guides/troubleshooting") }, - expanded = failedUpdatesListState.isScrollingUp() || failedUpdatesListState.isScrolledToEnd(), + // Revisit + // expanded = failedUpdatesListState.isScrollingUp() || failedUpdatesListState.isScrolledToEnd(), ) } }, @@ -129,7 +130,7 @@ class FailedUpdatesScreen : Screen() { state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) state.items.isEmpty() -> EmptyScreen( - textResource = R.string.information_no_update_errors, + stringRes = MR.strings.information_no_update_errors, modifier = Modifier.padding(contentPadding), happyFace = true, ) @@ -363,7 +364,7 @@ private fun FailedUpdatesAppBar( onClick = { onClickGroup(GroupByMode.NONE) }, ) } - AppBarActions(actions) + AppBarActions(actions.toImmutableList()) } }, scrollBehavior = scrollBehavior, @@ -399,7 +400,7 @@ private fun FailedUpdatesActionAppBar( icon = Icons.Outlined.FlipToBack, onClick = onInvertSelection, ), - ), + ).toImmutableList(), ) }, scrollBehavior = scrollBehavior, diff --git a/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesScreenModel.kt b/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesScreenModel.kt index 199d1d7fe1..d61dd1d976 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/failed/FailedUpdatesScreenModel.kt @@ -4,11 +4,10 @@ import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import cafe.adriel.voyager.core.model.StateScreenModel -import cafe.adriel.voyager.core.model.coroutineScope +import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.core.util.addOrRemove import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount -import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.source.online.HttpSource @@ -20,11 +19,11 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import logcat.LogPriority -import tachiyomi.core.preference.PreferenceStore -import tachiyomi.core.preference.getEnum -import tachiyomi.core.util.lang.launchIO -import tachiyomi.core.util.lang.launchNonCancellable -import tachiyomi.core.util.system.logcat +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.core.common.preference.getEnum +import tachiyomi.core.common.util.lang.launchIO +import tachiyomi.core.common.util.lang.launchNonCancellable +import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.model.Category import tachiyomi.domain.failed.repository.FailedUpdatesRepository @@ -56,7 +55,7 @@ class FailedUpdatesScreenModel( val channel = _channel.receiveAsFlow() init { - coroutineScope.launchIO { + screenModelScope.launchIO { val sortMode = preferenceStore.getEnum("sort_mode", SortingMode.BY_ALPHABET).get() combine( getSourcesWithFavoriteCount.subscribe(), @@ -79,10 +78,12 @@ class FailedUpdatesScreenModel( items = libraryManga.filter { libraryManga -> failedUpdates.any { it.mangaId == libraryManga.manga.id } }.map { libraryManga -> - val source = sourceManager.get(libraryManga.manga.source)!! + // Untrusted Extensions cause null crash + val source = sourceManager.getOrStub(libraryManga.manga.source) val failedUpdate = failedUpdates.find { it.mangaId == libraryManga.manga.id }!! val errorMessage = failedUpdate.errorMessage - val simplifiedErrorMessage = simplifyErrorMessage(errorMessage.substringBefore(":"), failedUpdate.isOnline) + val simplifiedErrorMessage = + simplifyErrorMessage(errorMessage.substringBefore(":"), failedUpdate.isOnline) FailedUpdatesManga( libraryManga = libraryManga, errorMessage = errorMessage, @@ -106,42 +107,56 @@ class FailedUpdatesScreenModel( private fun simplifyErrorMessage(exception: String, isOnline: Long): String { return when (exception) { // General networking exceptions - "SocketException" -> context.getString(R.string.exception_socket_error) - "BindException" -> context.getString(R.string.exception_bind_port) - "InterruptedIOException" -> context.getString(R.string.exception_io_interrupted) - "HttpRetryException" -> context.getString(R.string.exception_http_retry) - "PortUnreachableException" -> context.getString(R.string.exception_port_unreachable) + // Hold your arses this is temporary and for testing purposes only + "SocketException" -> "Socket Exception" + "BindException" -> "Bind Exception" + "InterruptedIOException" -> "Interrupted IO Exception" + "HttpRetryException" -> "HTTP Retry Exception" + "PortUnreachableException" -> "Port Unreachable Exception" // General IO-related exceptions - "IOException" -> if (isOnline == 1L) context.getString(R.string.exception_io_error) else context.getString(R.string.exception_internet_connection) - "TimeoutException" -> context.getString(R.string.exception_timed_out) + "IOException" -> if (isOnline == + 1L + ) { + "IO Exception" + } else { + "IOException: No Internet" + } + "TimeoutException" -> "Timeout Exception" // SSL & Security-related - "SSLException" -> context.getString(R.string.exception_ssl_connection) - "CertificateExpiredException" -> context.getString(R.string.exception_ssl_certificate) - "CertificateNotYetValidException" -> context.getString(R.string.exception_ssl_not_valid) - "CertificateParsingException" -> context.getString(R.string.exception_ssl_parsing) - "CertificateEncodingException" -> context.getString(R.string.exception_ssl_encoding) - "UnrecoverableKeyException" -> context.getString(R.string.exception_unrecoverable_key) - "KeyManagementException" -> context.getString(R.string.exception_key_management) - "NoSuchAlgorithmException" -> context.getString(R.string.exception_algorithm) - "KeyStoreException" -> context.getString(R.string.exception_keystore) - "NoSuchProviderException" -> context.getString(R.string.exception_security_provider) - "SignatureException" -> context.getString(R.string.exception_signature_validation) - "InvalidKeySpecException" -> context.getString(R.string.exception_key_specification) + "SSLException" -> "SSL Exception" + "CertificateExpiredException" -> "Certificate Expired Exception" + "CertificateNotYetValidException" -> "Certificate Not Yet Valid Exception" + "CertificateParsingException" -> "Certificate Parsing Exception" + "CertificateEncodingException" -> "Certificate Encoding Exception" + "UnrecoverableKeyException" -> "Unrecoverable Key Exception" + "KeyManagementException" -> "Key Management Exception" + "NoSuchAlgorithmException" -> "No Such Algorithm Exception" + "KeyStoreException" -> "Key Store Exception" + "NoSuchProviderException" -> "No Such Provider Exception" + "SignatureException" -> "Signature Exception" + "InvalidKeySpecException" -> "Invalid Key Spec Exception" // Host & DNS-related - "UnknownHostException" -> if (isOnline == 1L) context.getString(R.string.exception_domain) else context.getString(R.string.exception_internet_connection) - "NoRouteToHostException" -> context.getString(R.string.exception_route_to_host) + "UnknownHostException" -> if (isOnline == + 1L + ) { + "Unknown Host Exception" + } else { + "Unknown Host Exception: No Internet" + } + "ConnectException" -> "Connect Exception" + "NoRouteToHostException" -> "No Route To Host Exception" // URL & URI related - "URISyntaxException" -> context.getString(R.string.exception_uri_syntax) - "MalformedURLException" -> context.getString(R.string.exception_malformed_url) + "URISyntaxException" -> "URI Syntax Exception" + "MalformedURLException" -> "Malformed URL Exception" // Authentication & Proxy - "ProtocolException" -> context.getString(R.string.exception_protocol_proxy_type) + "ProtocolException" -> "Protocol Exception" // Concurrency & Operation-related - "CancellationException" -> context.getString(R.string.exception_cancelled) - "InterruptedException" -> context.getString(R.string.exception_interrupted) - "IllegalStateException" -> context.getString(R.string.exception_unexpected_state) - "UnsupportedOperationException" -> context.getString(R.string.exception_not_supported) - "IllegalArgumentException" -> context.getString(R.string.exception_invalid_argument) - else -> "" + "CancellationException" -> "Cancellation Exception" + "InterruptedException" -> "Interrupted Exception" + "IllegalStateException" -> "Illegal State Exception" + "UnsupportedOperationException" -> "Unsupported Operation Exception" + "IllegalArgumentException" -> "Illegal Argument Exception" + else -> "Unknown: $exception" } } @@ -163,7 +178,13 @@ class FailedUpdatesScreenModel( val descendingOrder = if (state.sortMode == SortingMode.BY_ALPHABET) !state.descendingOrder else false preferenceStore.getBoolean("descending_order", false).set(descendingOrder) state.copy( - items = if (descendingOrder) state.items.sortedByDescending { it.libraryManga.manga.title } else state.items.sortedBy { it.libraryManga.manga.title }, + items = if (descendingOrder) { + state.items.sortedByDescending { + it.libraryManga.manga.title + } + } else { + state.items.sortedBy { it.libraryManga.manga.title } + }, descendingOrder = descendingOrder, sortMode = SortingMode.BY_ALPHABET, ) @@ -172,7 +193,12 @@ class FailedUpdatesScreenModel( } @Composable - fun categoryMap(items: List, groupMode: GroupByMode, sortMode: SortingMode, descendingOrder: Boolean): Map, List>> { + fun categoryMap( + items: List, + groupMode: GroupByMode, + sortMode: SortingMode, + descendingOrder: Boolean, + ): Map, List>> { val unsortedMap = when (groupMode) { GroupByMode.BY_SOURCE -> items.groupBy { it.source.name } .mapValues { entry -> entry.value.groupBy { Pair(it.errorMessage, it.simplifiedErrorMessage) } } @@ -180,7 +206,14 @@ class FailedUpdatesScreenModel( } return when (sortMode) { SortingMode.BY_ALPHABET -> { - val sortedMap = TreeMap, List>>(if (descendingOrder) { compareByDescending { it } } else { compareBy { it } }) + val sortedMap = + TreeMap, List>>( + if (descendingOrder) { + compareByDescending { it } + } else { + compareBy { it } + }, + ) sortedMap.putAll(unsortedMap) sortedMap } @@ -276,7 +309,11 @@ class FailedUpdatesScreenModel( range.forEach { val inBetweenItem = getOrNull(it) - if (inBetweenItem != null && !inBetweenItem.selected && inBetweenItem.errorMessage == firstErrorMessage && inBetweenItem.errorMessage == lastErrorMessage) { + if (inBetweenItem != null && + !inBetweenItem.selected && + inBetweenItem.errorMessage == firstErrorMessage && + inBetweenItem.errorMessage == lastErrorMessage + ) { selectedMangaIds.add(inBetweenItem.libraryManga.manga.id) set(it, inBetweenItem.copy(selected = true)) } @@ -325,7 +362,7 @@ class FailedUpdatesScreenModel( } fun removeMangas(mangaList: List, deleteFromLibrary: Boolean, deleteChapters: Boolean) { - coroutineScope.launchNonCancellable { + screenModelScope.launchNonCancellable { val mangaToDelete = mangaList.distinctBy { it.id } if (deleteFromLibrary) { @@ -368,7 +405,7 @@ class FailedUpdatesScreenModel( ) } - coroutineScope.launchNonCancellable { failedUpdatesManager.removeFailedUpdatesByMangaIds(listOfMangaIds) } + screenModelScope.launchNonCancellable { failedUpdatesManager.removeFailedUpdatesByMangaIds(listOfMangaIds) } } fun openDeleteMangaDialog(selected: List) { 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 89b017aee0..f189b7a204 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 @@ -289,7 +289,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet is SourceNotInstalledException -> context.stringResource( MR.strings.loader_not_implemented_error, ) - else -> e.message ?: context.getString(MR.strings.exception_unknown) + else -> e.message ?: "context.getString(MR.strings.exception_unknown)" } try { failedUpdatesCount.getAndIncrement() @@ -322,7 +322,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet failedUpdatesCount.get(), ) } - } private fun downloadChapters(manga: Manga, chapters: List) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt index 6dcc9fed20..2f4be06322 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt @@ -41,7 +41,12 @@ object NotificationHandler { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP action = Constants.SHORTCUT_FAILED } - return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + return PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) } /** diff --git a/data/src/main/java/tachiyomi/data/failed/FailedUpdatesRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/failed/FailedUpdatesRepositoryImpl.kt index e45ac72c11..fd5dea1dde 100644 --- a/data/src/main/java/tachiyomi/data/failed/FailedUpdatesRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/failed/FailedUpdatesRepositoryImpl.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import logcat.LogPriority -import tachiyomi.core.util.system.logcat +import tachiyomi.core.common.util.system.logcat import tachiyomi.data.DatabaseHandler import tachiyomi.domain.failed.model.FailedUpdate import tachiyomi.domain.failed.repository.FailedUpdatesRepository