Update Manga in Expected Period (#5734)

* Add Predict Interval Test

* Get mangas next update and interval in library update

* Get next update and interval in backup restore

* Display and set intervals, nextUpdate in Manga Info

* Move logic function to MangeScreen and InfoHeader

Update per suggestion

---------

Co-authored-by: arkon <arkon@users.noreply.github.com>
This commit is contained in:
Quang Kieu
2023-07-23 18:12:01 -04:00
committed by arkon
parent 6d69caf59e
commit cb639f4e90
14 changed files with 460 additions and 131 deletions

View File

@@ -30,6 +30,7 @@ import eu.kanade.presentation.manga.EditCoverAction
import eu.kanade.presentation.manga.MangaScreen
import eu.kanade.presentation.manga.components.DeleteChaptersDialog
import eu.kanade.presentation.manga.components.MangaCoverDialog
import eu.kanade.presentation.manga.components.SetIntervalDialog
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.presentation.util.isTabletUi
@@ -53,6 +54,7 @@ import logcat.LogPriority
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.screens.LoadingScreen
@@ -100,6 +102,7 @@ class MangaScreen(
snackbarHostState = screenModel.snackbarHostState,
dateRelativeTime = screenModel.relativeTime,
dateFormat = screenModel.dateFormat,
intervalDisplay = screenModel::intervalDisplay,
isTabletUi = isTabletUi(),
chapterSwipeStartAction = screenModel.chapterSwipeStartAction,
chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
@@ -121,7 +124,8 @@ class MangaScreen(
onCoverClicked = screenModel::showCoverDialog,
onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
onEditCategoryClicked = screenModel::promptChangeCategories.takeIf { successState.manga.favorite },
onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.manga.favorite },
onEditIntervalClicked = screenModel::showSetMangaIntervalDialog.takeIf { MANGA_OUTSIDE_RELEASE_PERIOD in screenModel.libraryPreferences.libraryUpdateMangaRestriction().get() && successState.manga.favorite },
onMigrateClicked = { navigator.push(MigrateSearchScreen(successState.manga.id)) }.takeIf { successState.manga.favorite },
onMultiBookmarkClicked = screenModel::bookmarkChapters,
onMultiMarkAsReadClicked = screenModel::markChaptersRead,
@@ -207,6 +211,13 @@ class MangaScreen(
LoadingScreen(Modifier.systemBarsPadding())
}
}
is MangaScreenModel.Dialog.SetMangaInterval -> {
SetIntervalDialog(
interval = if (dialog.manga.calculateInterval < 0) -dialog.manga.calculateInterval else 0,
onDismissRequest = onDismissRequest,
onValueChanged = { screenModel.setFetchRangeInterval(dialog.manga, it) },
)
}
}
}

View File

@@ -69,20 +69,22 @@ import tachiyomi.domain.manga.interactor.GetMangaWithChapters
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.applyFilter
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.math.absoluteValue
class MangaScreenModel(
val context: Context,
val mangaId: Long,
private val isFromSource: Boolean,
private val downloadPreferences: DownloadPreferences = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
readerPreferences: ReaderPreferences = Injekt.get(),
uiPreferences: UiPreferences = Injekt.get(),
val libraryPreferences: LibraryPreferences = Injekt.get(),
val readerPreferences: ReaderPreferences = Injekt.get(),
val uiPreferences: UiPreferences = Injekt.get(),
private val trackManager: TrackManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(),
private val downloadCache: DownloadCache = Injekt.get(),
@@ -97,6 +99,7 @@ class MangaScreenModel(
private val getCategories: GetCategories = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
private val setMangaCategories: SetMangaCategories = Injekt.get(),
private val mangaRepository: MangaRepository = Injekt.get(),
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
) : StateScreenModel<MangaScreenModel.State>(State.Loading) {
@@ -307,7 +310,7 @@ class MangaScreenModel(
}
// Choose a category
else -> promptChangeCategories()
else -> showChangeCategoryDialog()
}
// Finally match with enhanced tracking when available
@@ -333,7 +336,7 @@ class MangaScreenModel(
}
}
fun promptChangeCategories() {
fun showChangeCategoryDialog() {
val manga = successState?.manga ?: return
coroutineScope.launch {
val categories = getCategories()
@@ -349,6 +352,39 @@ class MangaScreenModel(
}
}
fun showSetMangaIntervalDialog() {
val manga = successState?.manga ?: return
updateSuccessState {
it.copy(dialog = Dialog.SetMangaInterval(manga))
}
}
// TODO: this should be in the state/composables
fun intervalDisplay(): Pair<Int, Int>? {
val state = successState ?: return null
val leadDay = libraryPreferences.leadingExpectedDays().get()
val followDay = libraryPreferences.followingExpectedDays().get()
val effInterval = state.manga.calculateInterval
return 1.coerceAtLeast(effInterval.absoluteValue - leadDay) to (effInterval.absoluteValue + followDay)
}
fun setFetchRangeInterval(manga: Manga, newInterval: Int) {
val interval = when (newInterval) {
// reset interval 0 default to trigger recalculation
// only reset if interval is custom, which is negative
0 -> if (manga.calculateInterval < 0) 0 else manga.calculateInterval
else -> -newInterval
}
coroutineScope.launchIO {
updateManga.awaitUpdateFetchInterval(
manga.copy(calculateInterval = interval),
successState?.chapters?.map { it.chapter }.orEmpty(),
)
val newManga = mangaRepository.getMangaById(mangaId)
updateSuccessState { it.copy(manga = newManga) }
}
}
/**
* Returns true if the manga has any downloads.
*/
@@ -502,6 +538,7 @@ class MangaScreenModel(
chapters,
state.manga,
state.source,
manualFetch,
)
if (manualFetch) {
@@ -519,6 +556,8 @@ class MangaScreenModel(
coroutineScope.launch {
snackbarHostState.showSnackbar(message = message)
}
val newManga = mangaRepository.getMangaById(mangaId)
updateSuccessState { it.copy(manga = newManga, isRefreshingData = false) }
}
}
@@ -943,6 +982,7 @@ class MangaScreenModel(
data class ChangeCategory(val manga: Manga, val initialSelection: List<CheckboxState<Category>>) : Dialog
data class DeleteChapters(val chapters: List<Chapter>) : Dialog
data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog
data class SetMangaInterval(val manga: Manga) : Dialog
data object SettingsSheet : Dialog
data object TrackSheet : Dialog
data object FullCover : Dialog