mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	More refactoring of expected next update logic
This commit is contained in:
		@@ -50,13 +50,14 @@ class SyncChaptersWithSource(
 | 
			
		||||
        manga: Manga,
 | 
			
		||||
        source: Source,
 | 
			
		||||
        manualFetch: Boolean = false,
 | 
			
		||||
        zoneDateTime: ZonedDateTime = ZonedDateTime.now(),
 | 
			
		||||
        fetchRange: Pair<Long, Long> = Pair(0, 0),
 | 
			
		||||
        fetchWindow: Pair<Long, Long> = Pair(0, 0),
 | 
			
		||||
    ): List<Chapter> {
 | 
			
		||||
        if (rawSourceChapters.isEmpty() && !source.isLocal()) {
 | 
			
		||||
            throw NoChaptersException()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val now = ZonedDateTime.now()
 | 
			
		||||
 | 
			
		||||
        val sourceChapters = rawSourceChapters
 | 
			
		||||
            .distinctBy { it.url }
 | 
			
		||||
            .mapIndexed { i, sChapter ->
 | 
			
		||||
@@ -138,12 +139,11 @@ class SyncChaptersWithSource(
 | 
			
		||||
 | 
			
		||||
        // Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
 | 
			
		||||
        if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
 | 
			
		||||
            if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchRange.first) {
 | 
			
		||||
            if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
 | 
			
		||||
                updateManga.awaitUpdateFetchInterval(
 | 
			
		||||
                    manga,
 | 
			
		||||
                    dbChapters,
 | 
			
		||||
                    zoneDateTime,
 | 
			
		||||
                    fetchRange,
 | 
			
		||||
                    now,
 | 
			
		||||
                    fetchWindow,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            return emptyList()
 | 
			
		||||
@@ -200,8 +200,7 @@ class SyncChaptersWithSource(
 | 
			
		||||
            val chapterUpdates = toChange.map { it.toChapterUpdate() }
 | 
			
		||||
            updateChapter.awaitAll(chapterUpdates)
 | 
			
		||||
        }
 | 
			
		||||
        val newChapters = chapterRepository.getChapterByMangaId(manga.id)
 | 
			
		||||
        updateManga.awaitUpdateFetchInterval(manga, newChapters, zoneDateTime, fetchRange)
 | 
			
		||||
        updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
 | 
			
		||||
 | 
			
		||||
        // Set this manga as updated since chapters were changed
 | 
			
		||||
        // Note that last_update actually represents last time the chapter list changed at all
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ package eu.kanade.domain.manga.interactor
 | 
			
		||||
import eu.kanade.domain.manga.model.hasCustomCover
 | 
			
		||||
import eu.kanade.tachiyomi.data.cache.CoverCache
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import tachiyomi.domain.chapter.model.Chapter
 | 
			
		||||
import tachiyomi.domain.manga.interactor.SetFetchInterval
 | 
			
		||||
import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import tachiyomi.domain.manga.model.MangaUpdate
 | 
			
		||||
@@ -79,16 +78,12 @@ class UpdateManga(
 | 
			
		||||
 | 
			
		||||
    suspend fun awaitUpdateFetchInterval(
 | 
			
		||||
        manga: Manga,
 | 
			
		||||
        chapters: List<Chapter>,
 | 
			
		||||
        zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
 | 
			
		||||
        fetchRange: Pair<Long, Long> = setFetchInterval.getCurrent(zonedDateTime),
 | 
			
		||||
        dateTime: ZonedDateTime = ZonedDateTime.now(),
 | 
			
		||||
        window: Pair<Long, Long> = setFetchInterval.getWindow(dateTime),
 | 
			
		||||
    ): Boolean {
 | 
			
		||||
        val updatedManga = setFetchInterval.update(manga, chapters, zonedDateTime, fetchRange)
 | 
			
		||||
        return if (updatedManga != null) {
 | 
			
		||||
            mangaRepository.update(updatedManga)
 | 
			
		||||
        } else {
 | 
			
		||||
            true
 | 
			
		||||
        }
 | 
			
		||||
        return setFetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
 | 
			
		||||
            ?.let { mangaRepository.update(it) }
 | 
			
		||||
            ?: false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,6 @@ import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.model.Download
 | 
			
		||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.ChapterItem
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.FetchInterval
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
 | 
			
		||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
 | 
			
		||||
@@ -85,7 +84,7 @@ import java.util.Date
 | 
			
		||||
fun MangaScreen(
 | 
			
		||||
    state: MangaScreenModel.State.Success,
 | 
			
		||||
    snackbarHostState: SnackbarHostState,
 | 
			
		||||
    fetchInterval: FetchInterval?,
 | 
			
		||||
    fetchInterval: Int?,
 | 
			
		||||
    dateFormat: DateFormat,
 | 
			
		||||
    isTabletUi: Boolean,
 | 
			
		||||
    chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
 | 
			
		||||
@@ -217,7 +216,7 @@ private fun MangaScreenSmallImpl(
 | 
			
		||||
    state: MangaScreenModel.State.Success,
 | 
			
		||||
    snackbarHostState: SnackbarHostState,
 | 
			
		||||
    dateFormat: DateFormat,
 | 
			
		||||
    fetchInterval: FetchInterval?,
 | 
			
		||||
    fetchInterval: Int?,
 | 
			
		||||
    chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
 | 
			
		||||
    chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
 | 
			
		||||
    onBackClicked: () -> Unit,
 | 
			
		||||
@@ -448,7 +447,7 @@ fun MangaScreenLargeImpl(
 | 
			
		||||
    state: MangaScreenModel.State.Success,
 | 
			
		||||
    snackbarHostState: SnackbarHostState,
 | 
			
		||||
    dateFormat: DateFormat,
 | 
			
		||||
    fetchInterval: FetchInterval?,
 | 
			
		||||
    fetchInterval: Int?,
 | 
			
		||||
    chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
 | 
			
		||||
    chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
 | 
			
		||||
    onBackClicked: () -> Unit,
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.unit.DpSize
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import tachiyomi.domain.manga.interactor.MAX_GRACE_PERIOD
 | 
			
		||||
import tachiyomi.domain.manga.interactor.MAX_FETCH_INTERVAL
 | 
			
		||||
import tachiyomi.presentation.core.components.WheelTextPicker
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
@@ -56,7 +56,7 @@ fun SetIntervalDialog(
 | 
			
		||||
    onDismissRequest: () -> Unit,
 | 
			
		||||
    onValueChanged: (Int) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    var intervalValue by rememberSaveable { mutableIntStateOf(interval) }
 | 
			
		||||
    var selectedInterval by rememberSaveable { mutableIntStateOf(if (interval < 0) -interval else 0) }
 | 
			
		||||
 | 
			
		||||
    AlertDialog(
 | 
			
		||||
        onDismissRequest = onDismissRequest,
 | 
			
		||||
@@ -67,7 +67,7 @@ fun SetIntervalDialog(
 | 
			
		||||
                contentAlignment = Alignment.Center,
 | 
			
		||||
            ) {
 | 
			
		||||
                val size = DpSize(width = maxWidth / 2, height = 128.dp)
 | 
			
		||||
                val items = (0..MAX_GRACE_PERIOD).map {
 | 
			
		||||
                val items = (0..MAX_FETCH_INTERVAL).map {
 | 
			
		||||
                    if (it == 0) {
 | 
			
		||||
                        stringResource(R.string.label_default)
 | 
			
		||||
                    } else {
 | 
			
		||||
@@ -77,8 +77,8 @@ fun SetIntervalDialog(
 | 
			
		||||
                WheelTextPicker(
 | 
			
		||||
                    size = size,
 | 
			
		||||
                    items = items,
 | 
			
		||||
                    startIndex = intervalValue,
 | 
			
		||||
                    onSelectionChanged = { intervalValue = it },
 | 
			
		||||
                    startIndex = selectedInterval,
 | 
			
		||||
                    onSelectionChanged = { selectedInterval = it },
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
@@ -89,7 +89,7 @@ fun SetIntervalDialog(
 | 
			
		||||
        },
 | 
			
		||||
        confirmButton = {
 | 
			
		||||
            TextButton(onClick = {
 | 
			
		||||
                onValueChanged(intervalValue)
 | 
			
		||||
                onValueChanged(selectedInterval)
 | 
			
		||||
                onDismissRequest()
 | 
			
		||||
            },) {
 | 
			
		||||
                Text(text = stringResource(R.string.action_ok))
 | 
			
		||||
 
 | 
			
		||||
@@ -78,13 +78,13 @@ import coil.compose.AsyncImage
 | 
			
		||||
import eu.kanade.presentation.components.DropdownMenu
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.FetchInterval
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
 | 
			
		||||
import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import tachiyomi.presentation.core.components.material.TextButton
 | 
			
		||||
import tachiyomi.presentation.core.components.material.padding
 | 
			
		||||
import tachiyomi.presentation.core.util.clickableNoIndication
 | 
			
		||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
 | 
			
		||||
import kotlin.math.absoluteValue
 | 
			
		||||
import kotlin.math.roundToInt
 | 
			
		||||
 | 
			
		||||
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
 | 
			
		||||
@@ -166,7 +166,7 @@ fun MangaActionRow(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    favorite: Boolean,
 | 
			
		||||
    trackingCount: Int,
 | 
			
		||||
    fetchInterval: FetchInterval?,
 | 
			
		||||
    fetchInterval: Int?,
 | 
			
		||||
    isUserIntervalMode: Boolean,
 | 
			
		||||
    onAddToLibraryClicked: () -> Unit,
 | 
			
		||||
    onWebViewClicked: (() -> Unit)?,
 | 
			
		||||
@@ -190,14 +190,8 @@ fun MangaActionRow(
 | 
			
		||||
            onLongClick = onEditCategory,
 | 
			
		||||
        )
 | 
			
		||||
        if (onEditIntervalClicked != null && fetchInterval != null) {
 | 
			
		||||
            val intervalPair = 1.coerceAtLeast(fetchInterval.interval - fetchInterval.leadDays) to (fetchInterval.interval + fetchInterval.followDays)
 | 
			
		||||
            MangaActionButton(
 | 
			
		||||
                title =
 | 
			
		||||
                if (intervalPair.first == intervalPair.second) {
 | 
			
		||||
                    pluralStringResource(id = R.plurals.day, count = intervalPair.second, intervalPair.second)
 | 
			
		||||
                } else {
 | 
			
		||||
                    pluralStringResource(id = R.plurals.range_interval_day, count = intervalPair.second, intervalPair.first, intervalPair.second)
 | 
			
		||||
                },
 | 
			
		||||
                title = pluralStringResource(id = R.plurals.day, count = fetchInterval.absoluteValue, fetchInterval.absoluteValue),
 | 
			
		||||
                icon = Icons.Default.HourglassEmpty,
 | 
			
		||||
                color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
 | 
			
		||||
                onClick = onEditIntervalClicked,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,18 @@
 | 
			
		||||
package eu.kanade.presentation.more.settings.screen
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.StringRes
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.BoxWithConstraints
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.material3.AlertDialog
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.material3.TextButton
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.ReadOnlyComposable
 | 
			
		||||
import androidx.compose.runtime.collectAsState
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableIntStateOf
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.rememberCoroutineScope
 | 
			
		||||
import androidx.compose.runtime.saveable.rememberSaveable
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.platform.LocalContext
 | 
			
		||||
import androidx.compose.ui.res.pluralStringResource
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.text.style.TextAlign
 | 
			
		||||
import androidx.compose.ui.unit.DpSize
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.util.fastMap
 | 
			
		||||
import androidx.core.content.ContextCompat
 | 
			
		||||
import cafe.adriel.voyager.navigator.LocalNavigator
 | 
			
		||||
@@ -54,8 +39,6 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_U
 | 
			
		||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
 | 
			
		||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
 | 
			
		||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
 | 
			
		||||
import tachiyomi.domain.manga.interactor.MAX_GRACE_PERIOD
 | 
			
		||||
import tachiyomi.presentation.core.components.WheelTextPicker
 | 
			
		||||
import tachiyomi.presentation.core.util.collectAsState
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
@@ -141,13 +124,10 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
        val context = LocalContext.current
 | 
			
		||||
 | 
			
		||||
        val libraryUpdateIntervalPref = libraryPreferences.libraryUpdateInterval()
 | 
			
		||||
        val libraryUpdateDeviceRestrictionPref = libraryPreferences.libraryUpdateDeviceRestriction()
 | 
			
		||||
        val libraryUpdateMangaRestrictionPref = libraryPreferences.libraryUpdateMangaRestriction()
 | 
			
		||||
        val libraryUpdateCategoriesPref = libraryPreferences.libraryUpdateCategories()
 | 
			
		||||
        val libraryUpdateCategoriesExcludePref = libraryPreferences.libraryUpdateCategoriesExclude()
 | 
			
		||||
 | 
			
		||||
        val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState()
 | 
			
		||||
        val libraryUpdateMangaRestriction by libraryUpdateMangaRestrictionPref.collectAsState()
 | 
			
		||||
 | 
			
		||||
        val included by libraryUpdateCategoriesPref.collectAsState()
 | 
			
		||||
        val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
 | 
			
		||||
@@ -168,25 +148,10 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        val leadRange by libraryPreferences.leadingExpectedDays().collectAsState()
 | 
			
		||||
        val followRange by libraryPreferences.followingExpectedDays().collectAsState()
 | 
			
		||||
 | 
			
		||||
        var showFetchRangesDialog by rememberSaveable { mutableStateOf(false) }
 | 
			
		||||
        if (showFetchRangesDialog) {
 | 
			
		||||
            LibraryExpectedRangeDialog(
 | 
			
		||||
                initialLead = leadRange,
 | 
			
		||||
                initialFollow = followRange,
 | 
			
		||||
                onDismissRequest = { showFetchRangesDialog = false },
 | 
			
		||||
                onValueChanged = { leadValue, followValue ->
 | 
			
		||||
                    libraryPreferences.leadingExpectedDays().set(leadValue)
 | 
			
		||||
                    libraryPreferences.followingExpectedDays().set(followValue)
 | 
			
		||||
                    showFetchRangesDialog = false
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        return Preference.PreferenceGroup(
 | 
			
		||||
            title = stringResource(R.string.pref_category_library_update),
 | 
			
		||||
            preferenceItems = listOfNotNull(
 | 
			
		||||
            preferenceItems = listOf(
 | 
			
		||||
                Preference.PreferenceItem.ListPreference(
 | 
			
		||||
                    pref = libraryUpdateIntervalPref,
 | 
			
		||||
                    title = stringResource(R.string.pref_library_update_interval),
 | 
			
		||||
@@ -204,7 +169,7 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
                    },
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.MultiSelectListPreference(
 | 
			
		||||
                    pref = libraryUpdateDeviceRestrictionPref,
 | 
			
		||||
                    pref = libraryPreferences.libraryUpdateDeviceRestriction(),
 | 
			
		||||
                    enabled = libraryUpdateInterval > 0,
 | 
			
		||||
                    title = stringResource(R.string.pref_library_update_restriction),
 | 
			
		||||
                    subtitle = stringResource(R.string.restrictions),
 | 
			
		||||
@@ -241,7 +206,7 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
                    subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary),
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.MultiSelectListPreference(
 | 
			
		||||
                    pref = libraryUpdateMangaRestrictionPref,
 | 
			
		||||
                    pref = libraryPreferences.libraryUpdateMangaRestriction(),
 | 
			
		||||
                    title = stringResource(R.string.pref_library_update_manga_restriction),
 | 
			
		||||
                    entries = mapOf(
 | 
			
		||||
                        MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
 | 
			
		||||
@@ -250,17 +215,6 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
                        MANGA_OUTSIDE_RELEASE_PERIOD to stringResource(R.string.pref_update_only_in_release_period),
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.TextPreference(
 | 
			
		||||
                    title = stringResource(R.string.pref_update_release_grace_period),
 | 
			
		||||
                    subtitle = listOf(
 | 
			
		||||
                        pluralStringResource(R.plurals.pref_update_release_leading_days, leadRange, leadRange),
 | 
			
		||||
                        pluralStringResource(R.plurals.pref_update_release_following_days, followRange, followRange),
 | 
			
		||||
                    ).joinToString(),
 | 
			
		||||
                    onClick = { showFetchRangesDialog = true },
 | 
			
		||||
                ).takeIf { MANGA_OUTSIDE_RELEASE_PERIOD in libraryUpdateMangaRestriction },
 | 
			
		||||
                Preference.PreferenceItem.InfoPreference(
 | 
			
		||||
                    title = stringResource(R.string.pref_update_release_grace_period_info),
 | 
			
		||||
                ).takeIf { MANGA_OUTSIDE_RELEASE_PERIOD in libraryUpdateMangaRestriction },
 | 
			
		||||
                Preference.PreferenceItem.SwitchPreference(
 | 
			
		||||
                    pref = libraryPreferences.newShowUpdatesCount(),
 | 
			
		||||
                    title = stringResource(R.string.pref_library_update_show_tab_badge),
 | 
			
		||||
@@ -299,79 +253,4 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    	
 | 
			
		||||
    @Composable
 | 
			
		||||
    private fun LibraryExpectedRangeDialog(
 | 
			
		||||
        initialLead: Int,
 | 
			
		||||
        initialFollow: Int,
 | 
			
		||||
        onDismissRequest: () -> Unit,
 | 
			
		||||
        onValueChanged: (portrait: Int, landscape: Int) -> Unit,
 | 
			
		||||
    ) {
 | 
			
		||||
        var leadValue by rememberSaveable { mutableIntStateOf(initialLead) }
 | 
			
		||||
        var followValue by rememberSaveable { mutableIntStateOf(initialFollow) }
 | 
			
		||||
 | 
			
		||||
        AlertDialog(
 | 
			
		||||
            onDismissRequest = onDismissRequest,
 | 
			
		||||
            title = { Text(text = stringResource(R.string.pref_update_release_grace_period)) },
 | 
			
		||||
            text = {
 | 
			
		||||
                Column {
 | 
			
		||||
                    Row(
 | 
			
		||||
                        horizontalArrangement = Arrangement.spacedBy(8.dp),
 | 
			
		||||
                    ) {
 | 
			
		||||
                        Text(
 | 
			
		||||
                            modifier = Modifier.weight(1f),
 | 
			
		||||
                            text = pluralStringResource(R.plurals.pref_update_release_leading_days, leadValue, leadValue),
 | 
			
		||||
                            textAlign = TextAlign.Center,
 | 
			
		||||
                            maxLines = 1,
 | 
			
		||||
                            style = MaterialTheme.typography.labelMedium,
 | 
			
		||||
                        )
 | 
			
		||||
                        Text(
 | 
			
		||||
                            modifier = Modifier.weight(1f),
 | 
			
		||||
                            text = pluralStringResource(R.plurals.pref_update_release_following_days, followValue, followValue),
 | 
			
		||||
                            textAlign = TextAlign.Center,
 | 
			
		||||
                            maxLines = 1,
 | 
			
		||||
                            style = MaterialTheme.typography.labelMedium,
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                BoxWithConstraints(
 | 
			
		||||
                    modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                    contentAlignment = Alignment.Center,
 | 
			
		||||
                ) {
 | 
			
		||||
                    val size = DpSize(width = maxWidth / 2, height = 128.dp)
 | 
			
		||||
                    val items = (0..MAX_GRACE_PERIOD).map(Int::toString)
 | 
			
		||||
                    Row(
 | 
			
		||||
                        horizontalArrangement = Arrangement.spacedBy(8.dp),
 | 
			
		||||
                    ) {
 | 
			
		||||
                        WheelTextPicker(
 | 
			
		||||
                            size = size,
 | 
			
		||||
                            items = items,
 | 
			
		||||
                            startIndex = leadValue,
 | 
			
		||||
                            onSelectionChanged = {
 | 
			
		||||
                                leadValue = it
 | 
			
		||||
                            },
 | 
			
		||||
                        )
 | 
			
		||||
                        WheelTextPicker(
 | 
			
		||||
                            size = size,
 | 
			
		||||
                            items = items,
 | 
			
		||||
                            startIndex = followValue,
 | 
			
		||||
                            onSelectionChanged = {
 | 
			
		||||
                                followValue = it
 | 
			
		||||
                            },
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            dismissButton = {
 | 
			
		||||
                TextButton(onClick = onDismissRequest) {
 | 
			
		||||
                    Text(text = stringResource(android.R.string.cancel))
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            confirmButton = {
 | 
			
		||||
                TextButton(onClick = { onValueChanged(leadValue, followValue) }) {
 | 
			
		||||
                    Text(text = stringResource(R.string.action_ok))
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,8 +33,8 @@ class BackupRestorer(
 | 
			
		||||
    private val chapterRepository: ChapterRepository = Injekt.get()
 | 
			
		||||
    private val setFetchInterval: SetFetchInterval = Injekt.get()
 | 
			
		||||
 | 
			
		||||
    private var zonedDateTime = ZonedDateTime.now()
 | 
			
		||||
    private var currentFetchInterval = setFetchInterval.getCurrent(zonedDateTime)
 | 
			
		||||
    private var now = ZonedDateTime.now()
 | 
			
		||||
    private var currentFetchWindow = setFetchInterval.getWindow(now)
 | 
			
		||||
 | 
			
		||||
    private var backupManager = BackupManager(context)
 | 
			
		||||
 | 
			
		||||
@@ -102,8 +102,8 @@ class BackupRestorer(
 | 
			
		||||
        // Store source mapping for error messages
 | 
			
		||||
        val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
 | 
			
		||||
        sourceMapping = backupMaps.associate { it.sourceId to it.name }
 | 
			
		||||
        zonedDateTime = ZonedDateTime.now()
 | 
			
		||||
        currentFetchInterval = setFetchInterval.getCurrent(zonedDateTime)
 | 
			
		||||
        now = ZonedDateTime.now()
 | 
			
		||||
        currentFetchWindow = setFetchInterval.getWindow(now)
 | 
			
		||||
 | 
			
		||||
        return coroutineScope {
 | 
			
		||||
            // Restore individual manga
 | 
			
		||||
@@ -146,8 +146,7 @@ class BackupRestorer(
 | 
			
		||||
                // Fetch rest of manga information
 | 
			
		||||
                restoreNewManga(updatedManga, chapters, categories, history, tracks, backupCategories)
 | 
			
		||||
            }
 | 
			
		||||
            val updatedChapters = chapterRepository.getChapterByMangaId(restoredManga.id)
 | 
			
		||||
            updateManga.awaitUpdateFetchInterval(restoredManga, updatedChapters, zonedDateTime, currentFetchInterval)
 | 
			
		||||
            updateManga.awaitUpdateFetchInterval(restoredManga, now, currentFetchWindow)
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
 | 
			
		||||
            errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
 | 
			
		||||
 
 | 
			
		||||
@@ -231,9 +231,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
 | 
			
		||||
        val hasDownloads = AtomicBoolean(false)
 | 
			
		||||
        val restrictions = libraryPreferences.libraryUpdateMangaRestriction().get()
 | 
			
		||||
 | 
			
		||||
        val now = ZonedDateTime.now()
 | 
			
		||||
        val fetchInterval = setFetchInterval.getCurrent(now)
 | 
			
		||||
        val higherLimit = fetchInterval.second
 | 
			
		||||
        val fetchWindow by lazy { setFetchInterval.getWindow(ZonedDateTime.now()) }
 | 
			
		||||
 | 
			
		||||
        coroutineScope {
 | 
			
		||||
            mangaToUpdate.groupBy { it.manga.source }.values
 | 
			
		||||
@@ -255,8 +253,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
 | 
			
		||||
                                    manga,
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    when {
 | 
			
		||||
                                        MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && manga.nextUpdate > higherLimit ->
 | 
			
		||||
                                            skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_in_release_period))
 | 
			
		||||
                                        manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE ->
 | 
			
		||||
                                            skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_always_update))
 | 
			
		||||
 | 
			
		||||
                                        MANGA_NON_COMPLETED in restrictions && manga.status.toInt() == SManga.COMPLETED ->
 | 
			
		||||
                                            skippedUpdates.add(manga to context.getString(R.string.skipped_reason_completed))
 | 
			
		||||
@@ -267,12 +265,12 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
 | 
			
		||||
                                        MANGA_NON_READ in restrictions && libraryManga.totalChapters > 0L && !libraryManga.hasStarted ->
 | 
			
		||||
                                            skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_started))
 | 
			
		||||
 | 
			
		||||
                                        manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE ->
 | 
			
		||||
                                            skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_always_update))
 | 
			
		||||
                                        MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && manga.nextUpdate !in fetchWindow.first.rangeTo(fetchWindow.second) ->
 | 
			
		||||
                                            skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_in_release_period))
 | 
			
		||||
 | 
			
		||||
                                        else -> {
 | 
			
		||||
                                            try {
 | 
			
		||||
                                                val newChapters = updateManga(manga, now, fetchInterval)
 | 
			
		||||
                                                val newChapters = updateManga(manga, fetchWindow)
 | 
			
		||||
                                                    .sortedByDescending { it.sourceOrder }
 | 
			
		||||
 | 
			
		||||
                                                if (newChapters.isNotEmpty()) {
 | 
			
		||||
@@ -328,6 +326,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        if (skippedUpdates.isNotEmpty()) {
 | 
			
		||||
            // TODO: surface skipped reasons to user
 | 
			
		||||
            logcat {
 | 
			
		||||
                skippedUpdates
 | 
			
		||||
                    .groupBy { it.second }
 | 
			
		||||
                    .map { (reason, entries) -> "$reason: [${entries.map { it.first.title }.sorted().joinToString()}]" }
 | 
			
		||||
                    .joinToString()
 | 
			
		||||
            }
 | 
			
		||||
            notifier.showUpdateSkippedNotification(skippedUpdates.size)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -344,7 +349,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
 | 
			
		||||
     * @param manga the manga to update.
 | 
			
		||||
     * @return a pair of the inserted and removed chapters.
 | 
			
		||||
     */
 | 
			
		||||
    private suspend fun updateManga(manga: Manga, zoneDateTime: ZonedDateTime, fetchRange: Pair<Long, Long>): List<Chapter> {
 | 
			
		||||
    private suspend fun updateManga(manga: Manga, fetchWindow: Pair<Long, Long>): List<Chapter> {
 | 
			
		||||
        val source = sourceManager.getOrStub(manga.source)
 | 
			
		||||
 | 
			
		||||
        // Update manga metadata if needed
 | 
			
		||||
@@ -359,7 +364,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
 | 
			
		||||
        // to get latest data so it doesn't get overwritten later on
 | 
			
		||||
        val dbManga = getManga.await(manga.id)?.takeIf { it.favorite } ?: return emptyList()
 | 
			
		||||
 | 
			
		||||
        return syncChaptersWithSource.await(chapters, dbManga, source, false, zoneDateTime, fetchRange)
 | 
			
		||||
        return syncChaptersWithSource.await(chapters, dbManga, source, false, fetchWindow)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun updateCovers() {
 | 
			
		||||
 
 | 
			
		||||
@@ -83,13 +83,6 @@ class MangaScreen(
 | 
			
		||||
 | 
			
		||||
        val successState = state as MangaScreenModel.State.Success
 | 
			
		||||
        val isHttpSource = remember { successState.source is HttpSource }
 | 
			
		||||
        val fetchInterval = remember(successState.manga.fetchInterval) {
 | 
			
		||||
            FetchInterval(
 | 
			
		||||
                interval = successState.manga.fetchInterval,
 | 
			
		||||
                leadDays = screenModel.leadDay,
 | 
			
		||||
                followDays = screenModel.followDay,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        LaunchedEffect(successState.manga, screenModel.source) {
 | 
			
		||||
            if (isHttpSource) {
 | 
			
		||||
@@ -107,7 +100,7 @@ class MangaScreen(
 | 
			
		||||
            state = successState,
 | 
			
		||||
            snackbarHostState = screenModel.snackbarHostState,
 | 
			
		||||
            dateFormat = screenModel.dateFormat,
 | 
			
		||||
            fetchInterval = fetchInterval,
 | 
			
		||||
            fetchInterval = successState.manga.fetchInterval,
 | 
			
		||||
            isTabletUi = isTabletUi(),
 | 
			
		||||
            chapterSwipeStartAction = screenModel.chapterSwipeStartAction,
 | 
			
		||||
            chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
 | 
			
		||||
@@ -218,7 +211,7 @@ class MangaScreen(
 | 
			
		||||
            }
 | 
			
		||||
            is MangaScreenModel.Dialog.SetFetchInterval -> {
 | 
			
		||||
                SetIntervalDialog(
 | 
			
		||||
                    interval = if (dialog.manga.fetchInterval < 0) -dialog.manga.fetchInterval else 0,
 | 
			
		||||
                    interval = dialog.manga.fetchInterval,
 | 
			
		||||
                    onDismissRequest = onDismissRequest,
 | 
			
		||||
                    onValueChanged = { screenModel.setFetchInterval(dialog.manga, it) },
 | 
			
		||||
                )
 | 
			
		||||
 
 | 
			
		||||
@@ -129,8 +129,6 @@ class MangaScreenModel(
 | 
			
		||||
    private val skipFiltered by readerPreferences.skipFiltered().asState(coroutineScope)
 | 
			
		||||
 | 
			
		||||
    val isUpdateIntervalEnabled = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.libraryUpdateMangaRestriction().get()
 | 
			
		||||
    val leadDay = libraryPreferences.leadingExpectedDays().get()
 | 
			
		||||
    val followDay = libraryPreferences.followingExpectedDays().get()
 | 
			
		||||
 | 
			
		||||
    private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
 | 
			
		||||
    private val selectedChapterIds: HashSet<Long> = HashSet()
 | 
			
		||||
@@ -361,20 +359,14 @@ class MangaScreenModel(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setFetchInterval(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.fetchInterval < 0) 0 else manga.fetchInterval
 | 
			
		||||
            else -> -newInterval
 | 
			
		||||
        }
 | 
			
		||||
    fun setFetchInterval(manga: Manga, interval: Int) {
 | 
			
		||||
        coroutineScope.launchIO {
 | 
			
		||||
            updateManga.awaitUpdateFetchInterval(
 | 
			
		||||
                manga.copy(fetchInterval = interval),
 | 
			
		||||
                successState?.chapters?.map { it.chapter }.orEmpty(),
 | 
			
		||||
                // Custom intervals are negative
 | 
			
		||||
                manga.copy(fetchInterval = -interval),
 | 
			
		||||
            )
 | 
			
		||||
            val newManga = mangaRepository.getMangaById(mangaId)
 | 
			
		||||
            updateSuccessState { it.copy(manga = newManga) }
 | 
			
		||||
            val updatedManga = mangaRepository.getMangaById(manga.id)
 | 
			
		||||
            updateSuccessState { it.copy(manga = updatedManga) }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1055,10 +1047,3 @@ data class ChapterItem(
 | 
			
		||||
) {
 | 
			
		||||
    val isDownloaded = downloadState == Download.State.DOWNLOADED
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Immutable
 | 
			
		||||
data class FetchInterval(
 | 
			
		||||
    val interval: Int,
 | 
			
		||||
    val leadDays: Int,
 | 
			
		||||
    val followDays: Int,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user