From 6fd67bd240b15bf54ddb8144c005be5fc24f3bbb Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Sun, 20 Oct 2024 02:49:37 +0700 Subject: [PATCH] show all entries going to be checked for Smart update on Upcoming screen --- .../data/library/LibraryUpdateJob.kt | 19 ++--- .../mihon/feature/upcoming/UpcomingScreen.kt | 6 ++ .../feature/upcoming/UpcomingScreenContent.kt | 81 ++++++++++++++++--- .../feature/upcoming/UpcomingScreenModel.kt | 42 ++++++++++ .../upcoming/interactor/GetUpcomingManga.kt | 61 ++++++++++++++ 5 files changed, 189 insertions(+), 20 deletions(-) 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 2ec063a37..0f4bca672 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 @@ -260,6 +260,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet status.int == trackingExtra } } + LibraryGroup.BY_SOURCE -> { val sourceExtra = groupExtra?.nullIfBlank()?.toIntOrNull() val source = libraryManga.map { it.manga.source } @@ -269,12 +270,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet if (source != null) libraryManga.filter { it.manga.source == source } else emptyList() } + LibraryGroup.BY_STATUS -> { val statusExtra = groupExtra?.toLongOrNull() ?: -1 libraryManga.filter { it.manga.status == statusExtra } } + LibraryGroup.UNGROUPED -> libraryManga else -> libraryManga } @@ -314,20 +317,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet false } - MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && - ( - it.manga.nextUpdate > fetchWindowUpperBound || - // KMK --> - MANGA_NON_COMPLETED in restrictions && - it.manga.status.toInt() == SManga.COMPLETED - // KMK <-- - ) -> { + MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && it.manga.nextUpdate > fetchWindowUpperBound -> { skippedUpdates.add( it.manga to context.stringResource(MR.strings.skipped_reason_not_in_release_period), ) false } + else -> true } } @@ -462,6 +459,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet is SourceNotInstalledException -> context.stringResource( MR.strings.loader_not_implemented_error, ) + else -> e.message } failedUpdates.add(manga to errorMessage) @@ -730,7 +728,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet } return file } - } catch (_: Exception) {} + } catch (_: Exception) { + } return File("") } @@ -755,8 +754,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private const val ERROR_LOG_HELP_URL = "https://mihon.app/docs/guides/troubleshooting/" - private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60 - /** * Key for category to update. */ diff --git a/app/src/main/java/mihon/feature/upcoming/UpcomingScreen.kt b/app/src/main/java/mihon/feature/upcoming/UpcomingScreen.kt index 981c3d540..3d1a4c44a 100644 --- a/app/src/main/java/mihon/feature/upcoming/UpcomingScreen.kt +++ b/app/src/main/java/mihon/feature/upcoming/UpcomingScreen.kt @@ -8,6 +8,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.ui.manga.MangaScreen +import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD class UpcomingScreen : Screen() { @@ -22,6 +23,11 @@ class UpcomingScreen : Screen() { state = state, setSelectedYearMonth = screenModel::setSelectedYearMonth, onClickUpcoming = { navigator.push(MangaScreen(it.id)) }, + // KMK --> + showUpdatingMangas = screenModel::showUpdatingMangas, + hideUpdatingMangas = screenModel::hideUpdatingMangas, + isPredictReleaseDate = MANGA_OUTSIDE_RELEASE_PERIOD in screenModel.restriction, + // KMK <-- ) } } diff --git a/app/src/main/java/mihon/feature/upcoming/UpcomingScreenContent.kt b/app/src/main/java/mihon/feature/upcoming/UpcomingScreenContent.kt index 479d02be6..25d646130 100644 --- a/app/src/main/java/mihon/feature/upcoming/UpcomingScreenContent.kt +++ b/app/src/main/java/mihon/feature/upcoming/UpcomingScreenContent.kt @@ -9,9 +9,11 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.HelpOutline +import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material3.Badge import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -46,49 +48,83 @@ fun UpcomingScreenContent( state: UpcomingScreenModel.State, setSelectedYearMonth: (YearMonth) -> Unit, onClickUpcoming: (manga: Manga) -> Unit, + // KMK --> + showUpdatingMangas: () -> Unit, + hideUpdatingMangas: () -> Unit, + isPredictReleaseDate: Boolean, + // KMK <-- modifier: Modifier = Modifier, ) { val scope = rememberCoroutineScope() val listState = rememberLazyListState() + + // KMK --> + val headerIndexes = if (state.isShowingUpdatingMangas) state.updatingHeaderIndexes else state.headerIndexes + val items = if (state.isShowingUpdatingMangas) state.updatingItems else state.items + val events = if (state.isShowingUpdatingMangas) state.updatingEvents else state.events + // KMK <-- + val onClickDay: (LocalDate, Int) -> Unit = { date, offset -> - state.headerIndexes[date]?.let { + headerIndexes[date]?.let { scope.launch { listState.animateScrollToItem(it + offset) } } } Scaffold( - topBar = { UpcomingToolbar() }, + topBar = { + UpcomingToolbar( + // KMK --> + state.isShowingUpdatingMangas, + showUpdatingMangas = showUpdatingMangas, + hideUpdatingMangas = hideUpdatingMangas, + isPredictReleaseDate = isPredictReleaseDate, + // KMK <-- + ) + }, modifier = modifier, ) { paddingValues -> if (isTabletUi()) { UpcomingScreenLargeImpl( listState = listState, - items = state.items, - events = state.events, + items = items, + events = events, paddingValues = paddingValues, selectedYearMonth = state.selectedYearMonth, setSelectedYearMonth = setSelectedYearMonth, onClickDay = { onClickDay(it, 0) }, onClickUpcoming = onClickUpcoming, + // KMK --> + state.isShowingUpdatingMangas, + // KMK <-- ) } else { UpcomingScreenSmallImpl( listState = listState, - items = state.items, - events = state.events, + items = items, + events = events, paddingValues = paddingValues, selectedYearMonth = state.selectedYearMonth, setSelectedYearMonth = setSelectedYearMonth, onClickDay = { onClickDay(it, 1) }, onClickUpcoming = onClickUpcoming, + // KMK --> + state.isShowingUpdatingMangas, + // KMK <-- ) } } } @Composable -private fun UpcomingToolbar() { +private fun UpcomingToolbar( + // KMK --> + isShowingUpdatingMangas: Boolean, + showUpdatingMangas: () -> Unit, + hideUpdatingMangas: () -> Unit, + isPredictReleaseDate: Boolean, + // KMK <-- +) { val navigator = LocalNavigator.currentOrThrow val uriHandler = LocalUriHandler.current @@ -96,6 +132,25 @@ private fun UpcomingToolbar() { title = stringResource(MR.strings.label_upcoming), navigateUp = navigator::pop, actions = { + // KMK --> + if (isPredictReleaseDate) { + IconButton( + onClick = { + if (isShowingUpdatingMangas) { + hideUpdatingMangas() + } else { + showUpdatingMangas() + } + }, + ) { + Icon( + imageVector = Icons.Outlined.NewReleases, + contentDescription = stringResource(MR.strings.pref_library_update_smart_update), + tint = if (isShowingUpdatingMangas) MaterialTheme.colorScheme.primary else LocalContentColor.current, + ) + } + } + // KMK <-- IconButton(onClick = { uriHandler.openUri(Constants.URL_HELP_UPCOMING) }) { Icon( imageVector = Icons.AutoMirrored.Outlined.HelpOutline, @@ -143,6 +198,9 @@ private fun UpcomingScreenSmallImpl( setSelectedYearMonth: (YearMonth) -> Unit, onClickDay: (LocalDate) -> Unit, onClickUpcoming: (manga: Manga) -> Unit, + // KMK --> + isShowingUpdatingMangas: Boolean, + // KMK <-- ) { FastScrollLazyColumn( contentPadding = paddingValues, @@ -158,7 +216,7 @@ private fun UpcomingScreenSmallImpl( } items( items = items, - key = { "upcoming-${it.hashCode()}" }, + key = { (if (isShowingUpdatingMangas) "updating" else "upcoming") + it.hashCode() }, contentType = { when (it) { is UpcomingUIModel.Header -> "header" @@ -173,6 +231,7 @@ private fun UpcomingScreenSmallImpl( onClick = { onClickUpcoming(item.manga) }, ) } + is UpcomingUIModel.Header -> { DateHeading( date = item.date, @@ -194,6 +253,9 @@ private fun UpcomingScreenLargeImpl( setSelectedYearMonth: (YearMonth) -> Unit, onClickDay: (LocalDate) -> Unit, onClickUpcoming: (manga: Manga) -> Unit, + // KMK --> + isShowingUpdatingMangas: Boolean, + // KMK <-- ) { TwoPanelBox( modifier = Modifier.padding(paddingValues), @@ -209,7 +271,7 @@ private fun UpcomingScreenLargeImpl( FastScrollLazyColumn(state = listState) { items( items = items, - key = { "upcoming-${it.hashCode()}" }, + key = { (if (isShowingUpdatingMangas) "updating" else "upcoming") + it.hashCode() }, contentType = { when (it) { is UpcomingUIModel.Header -> "header" @@ -224,6 +286,7 @@ private fun UpcomingScreenLargeImpl( onClick = { onClickUpcoming(item.manga) }, ) } + is UpcomingUIModel.Header -> { DateHeading( date = item.date, diff --git a/app/src/main/java/mihon/feature/upcoming/UpcomingScreenModel.kt b/app/src/main/java/mihon/feature/upcoming/UpcomingScreenModel.kt index e72bb548d..e143e3f83 100644 --- a/app/src/main/java/mihon/feature/upcoming/UpcomingScreenModel.kt +++ b/app/src/main/java/mihon/feature/upcoming/UpcomingScreenModel.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mihon.domain.upcoming.interactor.GetUpcomingManga +import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.manga.model.Manga import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -25,6 +26,9 @@ import java.time.YearMonth class UpcomingScreenModel( private val getUpcomingManga: GetUpcomingManga = Injekt.get(), ) : StateScreenModel(State()) { + // KMK --> + private val libraryPreferences: LibraryPreferences = Injekt.get() + // KMK <-- init { screenModelScope.launch { @@ -39,6 +43,18 @@ class UpcomingScreenModel( } } } + // KMK --> + screenModelScope.launch { + mutableState.update { state -> + val updatingItems = getUpcomingManga.updatingMangas().toUpcomingUIModels() + state.copy( + updatingItems = updatingItems, + updatingEvents = updatingItems.toEvents(), + updatingHeaderIndexes = updatingItems.getHeaderIndexes(), + ) + } + } + // KMK <-- } private fun List.toUpcomingUIModels(): ImmutableList { @@ -81,10 +97,36 @@ class UpcomingScreenModel( mutableState.update { it.copy(selectedYearMonth = yearMonth) } } + // KMK --> + val restriction by lazy { libraryPreferences.autoUpdateMangaRestrictions().get() } + + fun showUpdatingMangas() { + mutableState.update { state -> + state.copy( + isShowingUpdatingMangas = true, + ) + } + } + + fun hideUpdatingMangas() { + mutableState.update { state -> + state.copy( + isShowingUpdatingMangas = false, + ) + } + } + // KMK <-- + data class State( val selectedYearMonth: YearMonth = YearMonth.now(), val items: ImmutableList = persistentListOf(), val events: ImmutableMap = persistentMapOf(), val headerIndexes: ImmutableMap = persistentMapOf(), + // KMK --> + val isShowingUpdatingMangas: Boolean = false, + val updatingItems: ImmutableList = persistentListOf(), + val updatingEvents: ImmutableMap = persistentMapOf(), + val updatingHeaderIndexes: ImmutableMap = persistentMapOf(), + // KMK <-- ) } diff --git a/domain/src/main/java/mihon/domain/upcoming/interactor/GetUpcomingManga.kt b/domain/src/main/java/mihon/domain/upcoming/interactor/GetUpcomingManga.kt index dd618b955..8ecb57d7a 100644 --- a/domain/src/main/java/mihon/domain/upcoming/interactor/GetUpcomingManga.kt +++ b/domain/src/main/java/mihon/domain/upcoming/interactor/GetUpcomingManga.kt @@ -1,13 +1,28 @@ package mihon.domain.upcoming.interactor import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.model.UpdateStrategy import kotlinx.coroutines.flow.Flow +import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_UNREAD +import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED +import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ +import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD +import tachiyomi.domain.manga.interactor.GetLibraryManga import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.repository.MangaRepository +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.time.LocalDate +import java.time.ZoneId class GetUpcomingManga( private val mangaRepository: MangaRepository, ) { + // KMK --> + private val libraryPreferences: LibraryPreferences = Injekt.get() + private val getLibraryManga: GetLibraryManga = Injekt.get() + // KMK <-- private val includedStatuses = setOf( SManga.ONGOING.toLong(), @@ -17,4 +32,50 @@ class GetUpcomingManga( suspend fun subscribe(): Flow> { return mangaRepository.getUpcomingManga(includedStatuses) } + + // KMK --> + suspend fun updatingMangas(): List { + val libraryManga = getLibraryManga.await() + + val categoriesToUpdate = libraryPreferences.updateCategories().get().map(String::toLong) + val includedManga = if (categoriesToUpdate.isNotEmpty()) { + libraryManga.filter { it.category in categoriesToUpdate } + } else { + libraryManga + } + + val categoriesToExclude = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() } + val excludedMangaIds = if (categoriesToExclude.isNotEmpty()) { + libraryManga.filter { it.category in categoriesToExclude }.map { it.manga.id } + } else { + emptyList() + } + + val listToUpdate = includedManga + .filterNot { it.manga.id in excludedMangaIds } + + val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get() + val today = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000 + + return listToUpdate + .distinctBy { it.manga.id } + .filter { + when { + it.manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE -> false + + MANGA_NON_COMPLETED in restrictions && it.manga.status.toInt() == SManga.COMPLETED -> false + + MANGA_HAS_UNREAD in restrictions && it.unreadCount != 0L -> false + + MANGA_NON_READ in restrictions && it.totalChapters > 0L && !it.hasStarted -> false + + MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && it.manga.nextUpdate < today -> false + + else -> true + } + } + .map { it.manga } + .sortedBy { it.nextUpdate } + } + // KMK <-- }