Error screen (#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>
This commit is contained in:
parent
e31a0016fb
commit
451fe433cf
39 changed files with 1547 additions and 51 deletions
40
app/src/main/java/eu/kanade/domain/KMKDomainModule.kt
Normal file
40
app/src/main/java/eu/kanade/domain/KMKDomainModule.kt
Normal file
|
|
@ -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<LibraryUpdateErrorWithRelationsRepository> {
|
||||||
|
LibraryUpdateErrorWithRelationsRepositoryImpl(get())
|
||||||
|
}
|
||||||
|
addFactory { GetLibraryUpdateErrorWithRelations(get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<LibraryUpdateErrorMessageRepository> { LibraryUpdateErrorMessageRepositoryImpl(get()) }
|
||||||
|
addFactory { GetLibraryUpdateErrorMessages(get()) }
|
||||||
|
addFactory { DeleteLibraryUpdateErrorMessages(get()) }
|
||||||
|
addFactory { InsertLibraryUpdateErrorMessages(get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<LibraryUpdateErrorRepository> { LibraryUpdateErrorRepositoryImpl(get()) }
|
||||||
|
addFactory { GetLibraryUpdateErrors(get()) }
|
||||||
|
addFactory { DeleteLibraryUpdateErrors(get()) }
|
||||||
|
addFactory { InsertLibraryUpdateErrors(get()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,410 @@
|
||||||
|
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
|
||||||
|
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.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.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
|
||||||
|
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.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
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.i18n.kmk.KMR
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
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<List<Int>>(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(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
LibraryUpdateErrorsAppBar(
|
||||||
|
title = stringResource(
|
||||||
|
KMR.strings.label_library_update_errors,
|
||||||
|
state.items.size,
|
||||||
|
),
|
||||||
|
itemCnt = state.items.size,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
selectedCount = state.selected.size,
|
||||||
|
onClickUnselectAll = { onSelectAll(false) },
|
||||||
|
onClickSelectAll = { onSelectAll(true) },
|
||||||
|
onClickInvertSelection = onInvertSelection,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { contentPadding ->
|
||||||
|
when {
|
||||||
|
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||||
|
state.items.isEmpty() -> EmptyScreen(
|
||||||
|
message = stringResource(KMR.strings.info_empty_library_update_errors),
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
FastScrollLazyColumn(
|
||||||
|
// Using modifier instead of contentPadding so we can use stickyHeader
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
state = listState,
|
||||||
|
) {
|
||||||
|
libraryUpdateErrorUiItems(
|
||||||
|
uiModels = state.getUiModel(),
|
||||||
|
selectionMode = state.selectionMode,
|
||||||
|
onErrorSelected = onErrorSelected,
|
||||||
|
onClick = onClick,
|
||||||
|
onClickCover = onClickCover,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LibraryUpdateErrorsBottomBar(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
selected: List<LibraryUpdateErrorItem>,
|
||||||
|
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)
|
||||||
|
.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,
|
||||||
|
tint = animatedColor,
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
color = animatedColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
content?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LibraryUpdateErrorsAppBar(
|
||||||
|
title: String,
|
||||||
|
itemCnt: Int,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
selectedCount: Int,
|
||||||
|
onClickUnselectAll: () -> Unit,
|
||||||
|
onClickSelectAll: () -> Unit,
|
||||||
|
onClickInvertSelection: () -> Unit,
|
||||||
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
|
) {
|
||||||
|
AppBar(
|
||||||
|
title = title,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
actions = {
|
||||||
|
if (itemCnt > 0) {
|
||||||
|
AppBarActions(
|
||||||
|
persistentListOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_select_all),
|
||||||
|
icon = Icons.Outlined.SelectAll,
|
||||||
|
onClick = onClickSelectAll,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
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.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.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
|
||||||
|
|
||||||
|
internal fun LazyListScope.libraryUpdateErrorUiItems(
|
||||||
|
uiModels: List<LibraryUpdateErrorUiModel>,
|
||||||
|
selectionMode: Boolean,
|
||||||
|
onErrorSelected: (LibraryUpdateErrorItem, Boolean, Boolean, Boolean) -> Unit,
|
||||||
|
onClick: (LibraryUpdateErrorItem) -> Unit,
|
||||||
|
onClickCover: (LibraryUpdateErrorItem) -> Unit,
|
||||||
|
) {
|
||||||
|
uiModels.forEach {
|
||||||
|
when (it) {
|
||||||
|
is LibraryUpdateErrorUiModel.Header -> {
|
||||||
|
stickyHeader(
|
||||||
|
key = "$STICKY_HEADER_KEY_PREFIX-errorHeader-${it.hashCode()}",
|
||||||
|
contentType = "header",
|
||||||
|
) {
|
||||||
|
ListGroupHeader(
|
||||||
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
|
text = it.errorMessage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is LibraryUpdateErrorUiModel.Item -> {
|
||||||
|
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,
|
||||||
|
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<SourceManager>().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()
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ import exh.pref.DelegateSourcePreferences
|
||||||
import tachiyomi.core.common.Constants
|
import tachiyomi.core.common.Constants
|
||||||
import tachiyomi.domain.UnsortedPreferences
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.i18n.kmk.KMR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
@ -64,6 +65,9 @@ fun MoreScreen(
|
||||||
onClickBatchAdd: () -> Unit,
|
onClickBatchAdd: () -> Unit,
|
||||||
onClickUpdates: () -> Unit,
|
onClickUpdates: () -> Unit,
|
||||||
onClickHistory: () -> Unit,
|
onClickHistory: () -> Unit,
|
||||||
|
// KMK -->
|
||||||
|
onClickLibraryUpdateErrors: () -> Unit,
|
||||||
|
// KMK <--
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
@ -183,6 +187,15 @@ fun MoreScreen(
|
||||||
onPreferenceClick = onClickStats,
|
onPreferenceClick = onClickStats,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// KMK -->
|
||||||
|
item {
|
||||||
|
TextPreferenceWidget(
|
||||||
|
title = stringResource(KMR.strings.option_label_library_update_errors),
|
||||||
|
icon = Icons.Outlined.NewReleases,
|
||||||
|
onPreferenceClick = onClickLibraryUpdateErrors,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(MR.strings.label_data_storage),
|
title = stringResource(MR.strings.label_data_storage),
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
|
||||||
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
|
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
|
||||||
import dev.mihon.injekt.patchInjekt
|
import dev.mihon.injekt.patchInjekt
|
||||||
import eu.kanade.domain.DomainModule
|
import eu.kanade.domain.DomainModule
|
||||||
|
import eu.kanade.domain.KMKDomainModule
|
||||||
import eu.kanade.domain.SYDomainModule
|
import eu.kanade.domain.SYDomainModule
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.sync.SyncPreferences
|
import eu.kanade.domain.sync.SyncPreferences
|
||||||
|
|
@ -126,6 +127,9 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
||||||
Injekt.importModule(SYPreferenceModule(this))
|
Injekt.importModule(SYPreferenceModule(this))
|
||||||
Injekt.importModule(SYDomainModule())
|
Injekt.importModule(SYDomainModule())
|
||||||
// SY <--
|
// SY <--
|
||||||
|
// KMK -->
|
||||||
|
Injekt.importModule(KMKDomainModule())
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
setupExhLogging() // EXH logging
|
setupExhLogging() // EXH logging
|
||||||
LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog
|
LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,6 @@ import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||||
import eu.kanade.tachiyomi.util.prepUpdateCover
|
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.isConnectedToWifi
|
||||||
import eu.kanade.tachiyomi.util.system.isRunning
|
import eu.kanade.tachiyomi.util.system.isRunning
|
||||||
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
||||||
|
|
@ -79,6 +77,11 @@ 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_COMPLETED
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
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.InsertLibraryUpdateErrorMessages
|
||||||
|
import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage
|
||||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||||
|
|
@ -95,7 +98,6 @@ import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
@ -134,6 +136,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
private val libraryUpdateStatus: LibraryUpdateStatus = Injekt.get()
|
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 <--
|
// KMK <--
|
||||||
|
|
||||||
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
|
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
|
||||||
|
|
@ -154,6 +159,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
libraryUpdateStatus.start()
|
libraryUpdateStatus.start()
|
||||||
|
|
||||||
|
deleteLibraryUpdateErrors.cleanUnrelevantMangaErrors()
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
||||||
setForegroundSafely()
|
setForegroundSafely()
|
||||||
|
|
@ -450,6 +457,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
// Convert to the manga that contains new chapters
|
// Convert to the manga that contains new chapters
|
||||||
newUpdates.add(manga to newChapters.toTypedArray())
|
newUpdates.add(manga to newChapters.toTypedArray())
|
||||||
}
|
}
|
||||||
|
clearErrorFromDB(mangaId = manga.id)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
val errorMessage = when (e) {
|
val errorMessage = when (e) {
|
||||||
is NoChaptersException ->
|
is NoChaptersException ->
|
||||||
|
|
@ -462,6 +470,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
|
|
||||||
else -> e.message
|
else -> e.message
|
||||||
}
|
}
|
||||||
|
writeErrorToDB(manga to errorMessage)
|
||||||
failedUpdates.add(manga to errorMessage)
|
failedUpdates.add(manga to errorMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -482,10 +491,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failedUpdates.isNotEmpty()) {
|
if (failedUpdates.isNotEmpty()) {
|
||||||
val errorFile = writeErrorFile(failedUpdates)
|
|
||||||
notifier.showUpdateErrorNotification(
|
notifier.showUpdateErrorNotification(
|
||||||
failedUpdates.size,
|
failedUpdates.size,
|
||||||
errorFile.getUriCompat(context),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -702,37 +709,40 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// KMK -->
|
||||||
* Writes basic file of update errors to cache dir.
|
private suspend fun clearErrorFromDB(mangaId: Long) {
|
||||||
*/
|
deleteLibraryUpdateErrors.deleteMangaError(mangaId = mangaId)
|
||||||
private fun writeErrorFile(errors: List<Pair<Manga, String?>>): 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("")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun writeErrorToDB(error: Pair<Manga, String?>) {
|
||||||
|
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<Pair<Manga, String?>>) {
|
||||||
|
val libraryErrors = errors.groupBy({ it.second }, { it.first })
|
||||||
|
val errorMessages = insertLibraryUpdateErrorMessages.insertAll(
|
||||||
|
libraryUpdateErrorMessages = libraryErrors.keys.map { errorMessage ->
|
||||||
|
LibraryUpdateErrorMessage(-1L, errorMessage.orEmpty())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val errorList = mutableListOf<LibraryUpdateError>()
|
||||||
|
errorMessages.forEach {
|
||||||
|
libraryErrors[it.second]?.forEach { manga ->
|
||||||
|
errorList.add(LibraryUpdateError(id = -1L, mangaId = manga.id, messageId = it.first))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insertLibraryUpdateErrors.insertAll(errorList)
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines what should be updated within a service execution.
|
* Defines what should be updated within a service execution.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import coil3.asDrawable
|
import coil3.asDrawable
|
||||||
|
|
@ -152,9 +151,8 @@ class LibraryUpdateNotifier(
|
||||||
* Shows notification containing update entries that failed with action to open full log.
|
* Shows notification containing update entries that failed with action to open full log.
|
||||||
*
|
*
|
||||||
* @param failed Number of entries that failed to update.
|
* @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) {
|
if (failed == 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -167,7 +165,7 @@ class LibraryUpdateNotifier(
|
||||||
setContentText(context.stringResource(MR.strings.action_show_errors))
|
setContentText(context.stringResource(MR.strings.action_show_errors))
|
||||||
setSmallIcon(R.drawable.ic_komikku)
|
setSmallIcon(R.drawable.ic_komikku)
|
||||||
|
|
||||||
setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri))
|
setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -634,6 +634,27 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||||
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
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.
|
* Returns [PendingIntent] that cancels a backup restore job.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import eu.kanade.tachiyomi.ui.browse.BrowseTab
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryTab
|
import eu.kanade.tachiyomi.ui.history.HistoryTab
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryTab
|
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.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreTab
|
import eu.kanade.tachiyomi.ui.more.MoreTab
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesTab
|
import eu.kanade.tachiyomi.ui.updates.UpdatesTab
|
||||||
|
|
@ -188,8 +189,14 @@ object HomeScreen : Screen() {
|
||||||
if (it is Tab.Library && it.mangaIdToOpen != null) {
|
if (it is Tab.Library && it.mangaIdToOpen != null) {
|
||||||
navigator.push(MangaScreen(it.mangaIdToOpen))
|
navigator.push(MangaScreen(it.mangaIdToOpen))
|
||||||
}
|
}
|
||||||
if (it is Tab.More && it.toDownloads) {
|
if (it is Tab.More) {
|
||||||
navigator.push(DownloadQueueScreen)
|
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 Updates : Tab
|
||||||
data object History : Tab
|
data object History : Tab
|
||||||
data class Browse(val toExtensions: Boolean = false) : 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<UnsortedPreferences>().skipPreMigration().get(),
|
||||||
|
navigator,
|
||||||
|
listOf(item.error.mangaId),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClickCover = { item -> navigator.push(MangaScreen(item.error.mangaId)) },
|
||||||
|
onMultiMigrateClicked = {
|
||||||
|
PreMigrationScreen.navigateToMigration(
|
||||||
|
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
||||||
|
navigator,
|
||||||
|
state.selected.map { it.error.mangaId },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onSelectAll = screenModel::toggleAllSelection,
|
||||||
|
onInvertSelection = screenModel::invertSelection,
|
||||||
|
onErrorSelected = screenModel::toggleSelection,
|
||||||
|
navigateUp = navigator::pop,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
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>(LibraryUpdateErrorScreenState()) {
|
||||||
|
|
||||||
|
// First and last selected index in list
|
||||||
|
private val selectedPositions: Array<Int> = arrayOf(-1, -1)
|
||||||
|
private val selectedErrorIds: HashSet<Long> = 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<LibraryUpdateErrorWithRelations>): List<LibraryUpdateErrorItem> {
|
||||||
|
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<LibraryUpdateErrorItem> = emptyList(),
|
||||||
|
val messages: List<LibraryUpdateErrorMessage> = emptyList(),
|
||||||
|
) {
|
||||||
|
|
||||||
|
val selected = items.filter { it.selected }
|
||||||
|
val selectionMode = selected.isNotEmpty()
|
||||||
|
|
||||||
|
fun getUiModel(): List<LibraryUpdateErrorUiModel> {
|
||||||
|
val uiModels = mutableListOf<LibraryUpdateErrorUiModel>()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHeaderIndexes(): List<Int> = getUiModel()
|
||||||
|
.withIndex()
|
||||||
|
.filter { it.value is LibraryUpdateErrorUiModel.Header }
|
||||||
|
.map { it.index }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class LibraryUpdateErrorItem(
|
||||||
|
val error: LibraryUpdateErrorWithRelations,
|
||||||
|
val selected: Boolean,
|
||||||
|
)
|
||||||
|
|
@ -544,6 +544,12 @@ class MainActivity : BaseActivity() {
|
||||||
navigator.popUntilRoot()
|
navigator.popUntilRoot()
|
||||||
HomeScreen.Tab.More(toDownloads = true)
|
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" -> {
|
Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> {
|
||||||
// If the intent match the "standard" Android search intent
|
// 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)
|
// or the Google-specific search intent (triggered by saying or typing "search *query* on *Tachiyomi*" in Google Search/Google Assistant)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryTab
|
import eu.kanade.tachiyomi.ui.history.HistoryTab
|
||||||
|
import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorScreen
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsScreen
|
import eu.kanade.tachiyomi.ui.setting.SettingsScreen
|
||||||
import eu.kanade.tachiyomi.ui.stats.StatsScreen
|
import eu.kanade.tachiyomi.ui.stats.StatsScreen
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesTab
|
import eu.kanade.tachiyomi.ui.updates.UpdatesTab
|
||||||
|
|
@ -89,6 +90,9 @@ object MoreTab : Tab {
|
||||||
onClickUpdates = { navigator.push(UpdatesTab) },
|
onClickUpdates = { navigator.push(UpdatesTab) },
|
||||||
onClickHistory = { navigator.push(HistoryTab) },
|
onClickHistory = { navigator.push(HistoryTab) },
|
||||||
// SY <--
|
// SY <--
|
||||||
|
// KMK -->
|
||||||
|
onClickLibraryUpdateErrors = { navigator.push(LibraryUpdateErrorScreen()) },
|
||||||
|
// KMK <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,8 @@ object Constants {
|
||||||
const val SHORTCUT_SOURCES = "eu.kanade.tachiyomi.SHOW_CATALOGUES"
|
const val SHORTCUT_SOURCES = "eu.kanade.tachiyomi.SHOW_CATALOGUES"
|
||||||
const val SHORTCUT_EXTENSIONS = "eu.kanade.tachiyomi.EXTENSIONS"
|
const val SHORTCUT_EXTENSIONS = "eu.kanade.tachiyomi.EXTENSIONS"
|
||||||
const val SHORTCUT_DOWNLOADS = "eu.kanade.tachiyomi.SHOW_DOWNLOADS"
|
const val SHORTCUT_DOWNLOADS = "eu.kanade.tachiyomi.SHOW_DOWNLOADS"
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
const val SHORTCUT_LIBRARY_UPDATE_ERRORS = "eu.kanade.tachiyomi.SHOW_LIBRARY_UPDATE_ERRORS"
|
||||||
|
// KMK <--
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
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<LibraryUpdateError> {
|
||||||
|
return handler.awaitList {
|
||||||
|
libraryUpdateErrorQueries.getAllErrors(
|
||||||
|
libraryUpdateErrorMapper,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAllAsFlow(): Flow<List<LibraryUpdateError>> {
|
||||||
|
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 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(
|
||||||
|
mangaId = libraryUpdateError.mangaId,
|
||||||
|
messageId = libraryUpdateError.messageId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertAll(libraryUpdateErrors: List<LibraryUpdateError>) {
|
||||||
|
return handler.await(inTransaction = true) {
|
||||||
|
libraryUpdateErrors.forEach {
|
||||||
|
libraryUpdateErrorQueries.insert(
|
||||||
|
mangaId = it.mangaId,
|
||||||
|
messageId = it.messageId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
ogUrl = mangaThumbnail,
|
||||||
|
lastModified = coverLastModified,
|
||||||
|
),
|
||||||
|
errorId = errorId,
|
||||||
|
messageId = messageId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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<List<LibraryUpdateErrorWithRelations>> {
|
||||||
|
return handler.subscribeToList {
|
||||||
|
libraryUpdateErrorViewQueries.errors(
|
||||||
|
libraryUpdateErrorWithRelationsMapper,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
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<LibraryUpdateErrorMessage> {
|
||||||
|
return handler.awaitList {
|
||||||
|
libraryUpdateErrorMessageQueries.getAllErrorMessages(
|
||||||
|
LibraryUpdateErrorMessageMapper,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAllAsFlow(): Flow<List<LibraryUpdateErrorMessage>> {
|
||||||
|
return handler.subscribeToList {
|
||||||
|
libraryUpdateErrorMessageQueries.getAllErrorMessages(
|
||||||
|
LibraryUpdateErrorMessageMapper,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteAll() {
|
||||||
|
return handler.await { libraryUpdateErrorMessageQueries.deleteAllErrorMessages() }
|
||||||
|
}
|
||||||
|
|
||||||
|
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<LibraryUpdateErrorMessage>,
|
||||||
|
): List<Pair<Long, String>> {
|
||||||
|
return handler.await(inTransaction = true) {
|
||||||
|
libraryUpdateErrorMessages.map {
|
||||||
|
libraryUpdateErrorMessageQueries.insert(it.message)
|
||||||
|
libraryUpdateErrorMessageQueries.selectLastInsertedRowId().executeAsOne() to it.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
CREATE TABLE libraryUpdateError (
|
||||||
|
_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
manga_id INTEGER NOT NULL UNIQUE,
|
||||||
|
message_id INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
getAllErrors:
|
||||||
|
SELECT *
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
CREATE TABLE libraryUpdateErrorMessage (
|
||||||
|
_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
message TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
getAllErrorMessages:
|
||||||
|
SELECT *
|
||||||
|
FROM libraryUpdateErrorMessage;
|
||||||
|
|
||||||
|
getErrorMessages:
|
||||||
|
SELECT *
|
||||||
|
FROM libraryUpdateErrorMessage WHERE message == :message;
|
||||||
|
|
||||||
|
insert:
|
||||||
|
INSERT INTO libraryUpdateErrorMessage(message) VALUES (:message);
|
||||||
|
|
||||||
|
deleteAllErrorMessages:
|
||||||
|
DELETE FROM libraryUpdateErrorMessage;
|
||||||
|
|
||||||
|
selectLastInsertedRowId:
|
||||||
|
SELECT last_insert_rowid();
|
||||||
26
data/src/main/sqldelight/tachiyomi/migrations/35.sqm
Normal file
26
data/src/main/sqldelight/tachiyomi/migrations/35.sqm
Normal file
|
|
@ -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 UNIQUE,
|
||||||
|
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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
data object Success : Result()
|
||||||
|
data class InternalError(val error: Throwable) : Result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<List<LibraryUpdateErrorWithRelations>> {
|
||||||
|
return libraryUpdateErrorWithRelationsRepository.subscribeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<List<LibraryUpdateError>> {
|
||||||
|
return libraryUpdateErrorRepository.getAllAsFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(): List<LibraryUpdateError> {
|
||||||
|
return libraryUpdateErrorRepository.getAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +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 upsert(libraryUpdateError: LibraryUpdateError) = withNonCancellableContext {
|
||||||
|
try {
|
||||||
|
libraryUpdateErrorRepository.upsert(libraryUpdateError)
|
||||||
|
Result.Success
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
return@withNonCancellableContext Result.InternalError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<LibraryUpdateError>) = withNonCancellableContext {
|
||||||
|
try {
|
||||||
|
libraryUpdateErrorRepository.insertAll(libraryUpdateErrors)
|
||||||
|
Result.Success
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
return@withNonCancellableContext Result.InternalError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package tachiyomi.domain.libraryUpdateError.repository
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import tachiyomi.domain.libraryUpdateError.model.LibraryUpdateError
|
||||||
|
|
||||||
|
interface LibraryUpdateErrorRepository {
|
||||||
|
|
||||||
|
suspend fun getAll(): List<LibraryUpdateError>
|
||||||
|
|
||||||
|
fun getAllAsFlow(): Flow<List<LibraryUpdateError>>
|
||||||
|
|
||||||
|
suspend fun deleteAll()
|
||||||
|
|
||||||
|
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<LibraryUpdateError>)
|
||||||
|
}
|
||||||
|
|
@ -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<List<LibraryUpdateErrorWithRelations>>
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
data object Success : Result()
|
||||||
|
data class InternalError(val error: Throwable) : Result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<List<LibraryUpdateErrorMessage>> {
|
||||||
|
return libraryUpdateErrorMessageRepository.getAllAsFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(): List<LibraryUpdateErrorMessage> {
|
||||||
|
return libraryUpdateErrorMessageRepository.getAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
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 get(message: String): Long? {
|
||||||
|
return libraryUpdateErrorMessageRepository.get(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long {
|
||||||
|
return libraryUpdateErrorMessageRepository.insert(libraryUpdateErrorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun insertAll(libraryUpdateErrorMessages: List<LibraryUpdateErrorMessage>): List<Pair<Long, String>> {
|
||||||
|
return libraryUpdateErrorMessageRepository.insertAll(libraryUpdateErrorMessages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package tachiyomi.domain.libraryUpdateErrorMessage.model
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class LibraryUpdateErrorMessage(
|
||||||
|
val id: Long,
|
||||||
|
val message: String,
|
||||||
|
) : Serializable
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package tachiyomi.domain.libraryUpdateErrorMessage.repository
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import tachiyomi.domain.libraryUpdateErrorMessage.model.LibraryUpdateErrorMessage
|
||||||
|
|
||||||
|
interface LibraryUpdateErrorMessageRepository {
|
||||||
|
|
||||||
|
suspend fun getAll(): List<LibraryUpdateErrorMessage>
|
||||||
|
|
||||||
|
fun getAllAsFlow(): Flow<List<LibraryUpdateErrorMessage>>
|
||||||
|
|
||||||
|
suspend fun deleteAll()
|
||||||
|
|
||||||
|
suspend fun get(message: String): Long?
|
||||||
|
|
||||||
|
suspend fun insert(libraryUpdateErrorMessage: LibraryUpdateErrorMessage): Long
|
||||||
|
|
||||||
|
suspend fun insertAll(libraryUpdateErrorMessages: List<LibraryUpdateErrorMessage>): List<Pair<Long, String>>
|
||||||
|
}
|
||||||
|
|
@ -135,6 +135,15 @@
|
||||||
<string name="saved_searches_add_feed">Saved Searches (Hold to add to Feed)</string>
|
<string name="saved_searches_add_feed">Saved Searches (Hold to add to Feed)</string>
|
||||||
<string name="saved_searches_delete">Saved Searches (Hold to delete)</string>
|
<string name="saved_searches_delete">Saved Searches (Hold to delete)</string>
|
||||||
|
|
||||||
|
<!-- Error Screen -->
|
||||||
|
<string name="option_label_library_update_errors">Library update errors</string>
|
||||||
|
<string name="label_library_update_errors">Library update errors (%d)</string>
|
||||||
|
<string name="info_empty_library_update_errors">You have no library update errors.</string>
|
||||||
|
<string name="action_scroll_to_top">Scroll to top</string>
|
||||||
|
<string name="action_scroll_to_bottom">Scroll to bottom</string>
|
||||||
|
<string name="action_scroll_to_previous">Scroll to previous</string>
|
||||||
|
<string name="action_scroll_to_next">Scroll to next</string>
|
||||||
|
|
||||||
<!-- Feed Tab -->
|
<!-- Feed Tab -->
|
||||||
<string name="too_many_in_feed">Too many sources in your feed, cannot add more than limited (20)</string>
|
<string name="too_many_in_feed">Too many sources in your feed, cannot add more than limited (20)</string>
|
||||||
<string name="action_sort_feed">Sort feeds</string>
|
<string name="action_sort_feed">Sort feeds</string>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package tachiyomi.presentation.core.components
|
package tachiyomi.presentation.core.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -13,15 +15,23 @@ fun ListGroupHeader(
|
||||||
text: String,
|
text: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Text(
|
// KMK -->
|
||||||
text = text,
|
Surface(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(
|
.fillMaxWidth(),
|
||||||
horizontal = MaterialTheme.padding.medium,
|
color = MaterialTheme.colorScheme.surfaceContainerLow,
|
||||||
vertical = MaterialTheme.padding.small,
|
) {
|
||||||
),
|
// KMK <--
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
Text(
|
||||||
fontWeight = FontWeight.SemiBold,
|
text = text,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
modifier = Modifier
|
||||||
)
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue