mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Add setting and calculate for update interval (#9399)
* Add Grace Period value and settings * Add functions to calculate nextUpdate * update per review * Move more into SetMangaUpdateInterval, keep wrapper
This commit is contained in:
		@@ -3,12 +3,16 @@ 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.getCurrentFetchRange
 | 
			
		||||
import tachiyomi.domain.manga.interactor.updateIntervalMeta
 | 
			
		||||
import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import tachiyomi.domain.manga.model.MangaUpdate
 | 
			
		||||
import tachiyomi.domain.manga.repository.MangaRepository
 | 
			
		||||
import tachiyomi.source.local.isLocal
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.time.ZonedDateTime
 | 
			
		||||
import java.util.Date
 | 
			
		||||
 | 
			
		||||
class UpdateManga(
 | 
			
		||||
@@ -73,6 +77,21 @@ class UpdateManga(
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun awaitUpdateIntervalMeta(
 | 
			
		||||
        manga: Manga,
 | 
			
		||||
        chapters: List<Chapter>,
 | 
			
		||||
        zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
 | 
			
		||||
        setCurrentFetchRange: Pair<Long, Long> = getCurrentFetchRange(zonedDateTime),
 | 
			
		||||
    ): Boolean {
 | 
			
		||||
        val newMeta = updateIntervalMeta(manga, chapters, zonedDateTime, setCurrentFetchRange)
 | 
			
		||||
 | 
			
		||||
        return if (newMeta != null) {
 | 
			
		||||
            mangaRepository.update(newMeta)
 | 
			
		||||
        } else {
 | 
			
		||||
            true
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
 | 
			
		||||
        return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Date().time))
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,13 @@
 | 
			
		||||
package eu.kanade.presentation.more.settings.screen
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.StringRes
 | 
			
		||||
import androidx.compose.foundation.layout.BoxWithConstraints
 | 
			
		||||
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
 | 
			
		||||
@@ -10,9 +17,14 @@ 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
 | 
			
		||||
@@ -39,6 +51,9 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY
 | 
			
		||||
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.presentation.core.components.WheelPickerDefaults
 | 
			
		||||
import tachiyomi.presentation.core.components.WheelTextPicker
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
@@ -132,8 +147,8 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
 | 
			
		||||
        val included by libraryUpdateCategoriesPref.collectAsState()
 | 
			
		||||
        val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
 | 
			
		||||
        var showDialog by rememberSaveable { mutableStateOf(false) }
 | 
			
		||||
        if (showDialog) {
 | 
			
		||||
        var showCategoriesDialog by rememberSaveable { mutableStateOf(false) }
 | 
			
		||||
        if (showCategoriesDialog) {
 | 
			
		||||
            TriStateListDialog(
 | 
			
		||||
                title = stringResource(R.string.categories),
 | 
			
		||||
                message = stringResource(R.string.pref_library_update_categories_details),
 | 
			
		||||
@@ -141,11 +156,27 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
                initialChecked = included.mapNotNull { id -> allCategories.find { it.id.toString() == id } },
 | 
			
		||||
                initialInversed = excluded.mapNotNull { id -> allCategories.find { it.id.toString() == id } },
 | 
			
		||||
                itemLabel = { it.visualName },
 | 
			
		||||
                onDismissRequest = { showDialog = false },
 | 
			
		||||
                onDismissRequest = { showCategoriesDialog = false },
 | 
			
		||||
                onValueChanged = { newIncluded, newExcluded ->
 | 
			
		||||
                    libraryUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
 | 
			
		||||
                    libraryUpdateCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet())
 | 
			
		||||
                    showDialog = false
 | 
			
		||||
                    showCategoriesDialog = false
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        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
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
@@ -192,8 +223,27 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
                        MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
 | 
			
		||||
                        MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
 | 
			
		||||
                        MANGA_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
 | 
			
		||||
                        MANGA_OUTSIDE_RELEASE_PERIOD to stringResource(R.string.pref_update_only_in_release_period),
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.TextPreference(
 | 
			
		||||
                    title = stringResource(R.string.pref_library_update_manga_restriction),
 | 
			
		||||
                    subtitle = setOf(
 | 
			
		||||
                        stringResource(R.string.pref_update_release_leading_days, leadRange),
 | 
			
		||||
                        stringResource(R.string.pref_update_release_following_days, followRange),
 | 
			
		||||
                    )
 | 
			
		||||
                        .joinToString(";"),
 | 
			
		||||
                    onClick = { showFetchRangesDialog = true },
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.InfoPreference(
 | 
			
		||||
                    title = stringResource(R.string.pref_update_release_grace_period_info1),
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.InfoPreference(
 | 
			
		||||
                    title = stringResource(R.string.pref_update_release_grace_period_info2),
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.InfoPreference(
 | 
			
		||||
                    title = stringResource(R.string.pref_update_release_grace_period_info3),
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.TextPreference(
 | 
			
		||||
                    title = stringResource(R.string.categories),
 | 
			
		||||
                    subtitle = getCategoriesLabel(
 | 
			
		||||
@@ -201,7 +251,7 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
                        included = included,
 | 
			
		||||
                        excluded = excluded,
 | 
			
		||||
                    ),
 | 
			
		||||
                    onClick = { showDialog = true },
 | 
			
		||||
                    onClick = { showCategoriesDialog = true },
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.SwitchPreference(
 | 
			
		||||
                    pref = libraryPreferences.autoUpdateMetadata(),
 | 
			
		||||
@@ -248,4 +298,82 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    	
 | 
			
		||||
    @Composable
 | 
			
		||||
    private fun LibraryExpectedRangeDialog(
 | 
			
		||||
        initialLead: Int,
 | 
			
		||||
        initialFollow: Int,
 | 
			
		||||
        onDismissRequest: () -> Unit,
 | 
			
		||||
        onValueChanged: (portrait: Int, landscape: Int) -> Unit,
 | 
			
		||||
    ) {
 | 
			
		||||
        val context = LocalContext.current
 | 
			
		||||
        var leadValue by rememberSaveable { mutableStateOf(initialLead) }
 | 
			
		||||
        var followValue by rememberSaveable { mutableStateOf(initialFollow) }
 | 
			
		||||
 | 
			
		||||
        AlertDialog(
 | 
			
		||||
            onDismissRequest = onDismissRequest,
 | 
			
		||||
            title = { Text(text = stringResource(R.string.pref_update_release_grace_period)) },
 | 
			
		||||
            text = {
 | 
			
		||||
                Row {
 | 
			
		||||
                    Text(
 | 
			
		||||
                        modifier = Modifier.weight(1f),
 | 
			
		||||
                        text = stringResource(R.string.pref_update_release_leading_days, "x"),
 | 
			
		||||
                        textAlign = TextAlign.Center,
 | 
			
		||||
                        maxLines = 1,
 | 
			
		||||
                        style = MaterialTheme.typography.labelMedium,
 | 
			
		||||
                    )
 | 
			
		||||
                    Text(
 | 
			
		||||
                        modifier = Modifier.weight(1f),
 | 
			
		||||
                        text = stringResource(R.string.pref_update_release_following_days, "x"),
 | 
			
		||||
                        textAlign = TextAlign.Center,
 | 
			
		||||
                        maxLines = 1,
 | 
			
		||||
                        style = MaterialTheme.typography.labelMedium,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                BoxWithConstraints(
 | 
			
		||||
                    modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                    contentAlignment = Alignment.Center,
 | 
			
		||||
                ) {
 | 
			
		||||
                    WheelPickerDefaults.Background(size = DpSize(maxWidth, maxHeight))
 | 
			
		||||
 | 
			
		||||
                    val size = DpSize(width = maxWidth / 2, height = 128.dp)
 | 
			
		||||
                    val items = (0..28).map {
 | 
			
		||||
                        if (it == 0) {
 | 
			
		||||
                            stringResource(R.string.label_default)
 | 
			
		||||
                        } else {
 | 
			
		||||
                            it.toString()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    Row {
 | 
			
		||||
                        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(android.R.string.ok))
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,9 +34,13 @@ class LibraryPreferences(
 | 
			
		||||
            MANGA_HAS_UNREAD,
 | 
			
		||||
            MANGA_NON_COMPLETED,
 | 
			
		||||
            MANGA_NON_READ,
 | 
			
		||||
            MANGA_OUTSIDE_RELEASE_PERIOD,
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    fun leadingExpectedDays() = preferenceStore.getInt("pref_library_before_expect_key", 1)
 | 
			
		||||
    fun followingExpectedDays() = preferenceStore.getInt("pref_library_after_expect_key", 1)
 | 
			
		||||
 | 
			
		||||
    fun autoUpdateMetadata() = preferenceStore.getBoolean("auto_update_metadata", false)
 | 
			
		||||
 | 
			
		||||
    fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
 | 
			
		||||
@@ -55,6 +59,16 @@ class LibraryPreferences(
 | 
			
		||||
 | 
			
		||||
    fun filterCompleted() = preferenceStore.getEnum("pref_filter_library_completed_v2", TriStateFilter.DISABLED)
 | 
			
		||||
 | 
			
		||||
    fun filterIntervalCustom() = preferenceStore.getEnum("pref_filter_library_interval_custom", TriStateFilter.DISABLED)
 | 
			
		||||
 | 
			
		||||
    fun filterIntervalLong() = preferenceStore.getEnum("pref_filter_library_interval_long", TriStateFilter.DISABLED)
 | 
			
		||||
 | 
			
		||||
    fun filterIntervalLate() = preferenceStore.getEnum("pref_filter_library_interval_late", TriStateFilter.DISABLED)
 | 
			
		||||
 | 
			
		||||
    fun filterIntervalDropped() = preferenceStore.getEnum("pref_filter_library_interval_dropped", TriStateFilter.DISABLED)
 | 
			
		||||
 | 
			
		||||
    fun filterIntervalPassed() = preferenceStore.getEnum("pref_filter_library_interval_passed", TriStateFilter.DISABLED)
 | 
			
		||||
 | 
			
		||||
    fun filterTracking(id: Int) = preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriStateFilter.DISABLED)
 | 
			
		||||
 | 
			
		||||
    // endregion
 | 
			
		||||
@@ -142,5 +156,6 @@ class LibraryPreferences(
 | 
			
		||||
        const val MANGA_NON_COMPLETED = "manga_ongoing"
 | 
			
		||||
        const val MANGA_HAS_UNREAD = "manga_fully_read"
 | 
			
		||||
        const val MANGA_NON_READ = "manga_started"
 | 
			
		||||
        const val MANGA_OUTSIDE_RELEASE_PERIOD = "manga_outside_release_period"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,108 @@
 | 
			
		||||
package tachiyomi.domain.manga.interactor
 | 
			
		||||
 | 
			
		||||
import tachiyomi.domain.chapter.model.Chapter
 | 
			
		||||
import tachiyomi.domain.library.service.LibraryPreferences
 | 
			
		||||
import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import tachiyomi.domain.manga.model.MangaUpdate
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.time.Instant
 | 
			
		||||
import java.time.ZonedDateTime
 | 
			
		||||
import java.time.temporal.ChronoUnit
 | 
			
		||||
import kotlin.math.absoluteValue
 | 
			
		||||
 | 
			
		||||
fun updateIntervalMeta(
 | 
			
		||||
    manga: Manga,
 | 
			
		||||
    chapters: List<Chapter>,
 | 
			
		||||
    zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
 | 
			
		||||
    setCurrentFetchRange: Pair<Long, Long> = getCurrentFetchRange(zonedDateTime),
 | 
			
		||||
): MangaUpdate? {
 | 
			
		||||
    val currentFetchRange = if (setCurrentFetchRange.first == 0L && setCurrentFetchRange.second == 0L) {
 | 
			
		||||
        getCurrentFetchRange(ZonedDateTime.now())
 | 
			
		||||
    } else {
 | 
			
		||||
        setCurrentFetchRange
 | 
			
		||||
    }
 | 
			
		||||
    val interval = manga.calculateInterval.takeIf { it < 0 } ?: calculateInterval(chapters, zonedDateTime)
 | 
			
		||||
    val nextUpdate = calculateNextUpdate(manga, interval, zonedDateTime, currentFetchRange)
 | 
			
		||||
 | 
			
		||||
    return if (manga.nextUpdate == nextUpdate && manga.calculateInterval == interval) {
 | 
			
		||||
        null
 | 
			
		||||
    } else { MangaUpdate(id = manga.id, nextUpdate = nextUpdate, calculateInterval = interval) }
 | 
			
		||||
}
 | 
			
		||||
fun calculateInterval(chapters: List<Chapter>, zonedDateTime: ZonedDateTime): Int {
 | 
			
		||||
    val sortChapters =
 | 
			
		||||
        chapters.sortedWith(compareBy<Chapter> { it.dateUpload }.thenBy { it.dateFetch })
 | 
			
		||||
            .reversed().take(50)
 | 
			
		||||
    val uploadDates = sortChapters.filter { it.dateUpload != 0L }.map {
 | 
			
		||||
        ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zonedDateTime.zone).toLocalDate()
 | 
			
		||||
            .atStartOfDay()
 | 
			
		||||
    }
 | 
			
		||||
    val uploadDateDistinct = uploadDates.distinctBy { it }
 | 
			
		||||
    val fetchDates = sortChapters.map {
 | 
			
		||||
        ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zonedDateTime.zone).toLocalDate()
 | 
			
		||||
            .atStartOfDay()
 | 
			
		||||
    }
 | 
			
		||||
    val fetchDatesDistinct = fetchDates.distinctBy { it }
 | 
			
		||||
    val newInterval = when {
 | 
			
		||||
        // enough upload date from source
 | 
			
		||||
        (uploadDateDistinct.size >= 3) -> {
 | 
			
		||||
            val uploadDelta = uploadDateDistinct.last().until(uploadDateDistinct.first(), ChronoUnit.DAYS)
 | 
			
		||||
            val uploadPeriod = uploadDates.indexOf(uploadDateDistinct.last())
 | 
			
		||||
            (uploadDelta).floorDiv(uploadPeriod).toInt()
 | 
			
		||||
        }
 | 
			
		||||
        // enough fetch date from client
 | 
			
		||||
        (fetchDatesDistinct.size >= 3) -> {
 | 
			
		||||
            val fetchDelta = fetchDatesDistinct.last().until(fetchDatesDistinct.first(), ChronoUnit.DAYS)
 | 
			
		||||
            val uploadPeriod = fetchDates.indexOf(fetchDatesDistinct.last())
 | 
			
		||||
            (fetchDelta).floorDiv(uploadPeriod).toInt()
 | 
			
		||||
        }
 | 
			
		||||
        // default 7 days
 | 
			
		||||
        else -> 7
 | 
			
		||||
    }
 | 
			
		||||
    // min 1, max 28 days
 | 
			
		||||
    return newInterval.coerceIn(1, 28)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun calculateNextUpdate(manga: Manga, interval: Int, zonedDateTime: ZonedDateTime, currentFetchRange: Pair<Long, Long>): Long {
 | 
			
		||||
    return if (manga.nextUpdate !in currentFetchRange.first.rangeTo(currentFetchRange.second + 1) ||
 | 
			
		||||
        manga.calculateInterval == 0
 | 
			
		||||
    ) {
 | 
			
		||||
        val latestDate = ZonedDateTime.ofInstant(Instant.ofEpochMilli(manga.lastUpdate), zonedDateTime.zone).toLocalDate().atStartOfDay()
 | 
			
		||||
        val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, zonedDateTime).toInt()
 | 
			
		||||
        val cycle = timeSinceLatest.floorDiv(interval.absoluteValue.takeIf { interval < 0 } ?: doubleInterval(interval, timeSinceLatest, doubleWhenOver = 10, maxValue = 28))
 | 
			
		||||
        latestDate.plusDays((cycle + 1) * interval.toLong()).toEpochSecond(zonedDateTime.offset) * 1000
 | 
			
		||||
    } else {
 | 
			
		||||
        manga.nextUpdate
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int, maxValue: Int): Int {
 | 
			
		||||
    if (delta >= maxValue) return maxValue
 | 
			
		||||
    val cycle = timeSinceLatest.floorDiv(delta) + 1
 | 
			
		||||
    // double delta again if missed more than 9 check in new delta
 | 
			
		||||
    return if (cycle > doubleWhenOver) {
 | 
			
		||||
        doubleInterval(delta * 2, timeSinceLatest, doubleWhenOver, maxValue)
 | 
			
		||||
    } else {
 | 
			
		||||
        delta
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun getCurrentFetchRange(
 | 
			
		||||
    timeToCal: ZonedDateTime,
 | 
			
		||||
): Pair<Long, Long> {
 | 
			
		||||
    val preferences: LibraryPreferences = Injekt.get()
 | 
			
		||||
 | 
			
		||||
    // lead range and the following range depend on if updateOnlyExpectedPeriod set.
 | 
			
		||||
    var followRange = 0
 | 
			
		||||
    var leadRange = 0
 | 
			
		||||
    if (LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in preferences.libraryUpdateMangaRestriction().get()) {
 | 
			
		||||
        followRange = preferences.followingExpectedDays().get()
 | 
			
		||||
        leadRange = preferences.leadingExpectedDays().get()
 | 
			
		||||
    }
 | 
			
		||||
    val startToday = timeToCal.toLocalDate().atStartOfDay(timeToCal.zone)
 | 
			
		||||
    // revert math of (next_update + follow < now) become (next_update < now - follow)
 | 
			
		||||
    // so (now - follow) become lower limit
 | 
			
		||||
    val lowerRange = startToday.minusDays(followRange.toLong())
 | 
			
		||||
    val higherRange = startToday.plusDays(leadRange.toLong())
 | 
			
		||||
    return Pair(lowerRange.toEpochSecond() * 1000, higherRange.toEpochSecond() * 1000 - 1)
 | 
			
		||||
}
 | 
			
		||||
@@ -44,9 +44,15 @@
 | 
			
		||||
    <string name="action_settings">Settings</string>
 | 
			
		||||
    <string name="action_menu">Menu</string>
 | 
			
		||||
    <string name="action_filter">Filter</string>
 | 
			
		||||
    <string name="action_set_interval">Set interval</string>
 | 
			
		||||
    <string name="action_filter_bookmarked">Bookmarked</string>
 | 
			
		||||
    <string name="action_filter_tracked">Tracked</string>
 | 
			
		||||
    <string name="action_filter_unread">Unread</string>
 | 
			
		||||
    <string name="action_filter_interval_custom">Customized fetch interval</string>
 | 
			
		||||
    <string name="action_filter_interval_long">Fetch monthly (28 days)</string>
 | 
			
		||||
    <string name="action_filter_interval_late">Late 10+ check</string>
 | 
			
		||||
    <string name="action_filter_interval_dropped">Dropped? Late 20+ and 2 months</string>
 | 
			
		||||
    <string name="action_filter_interval_passed">Passed check period</string>
 | 
			
		||||
    <!-- reserved for #4048 -->
 | 
			
		||||
    <string name="action_filter_empty">Remove filter</string>
 | 
			
		||||
    <string name="action_sort_alpha">Alphabetically</string>
 | 
			
		||||
@@ -55,6 +61,7 @@
 | 
			
		||||
    <string name="action_sort_last_read">Last read</string>
 | 
			
		||||
    <string name="action_sort_last_manga_update">Last update check</string>
 | 
			
		||||
    <string name="action_sort_unread_count">Unread count</string>
 | 
			
		||||
    <string name="action_sort_next_updated">Next expected update</string>
 | 
			
		||||
    <string name="action_sort_latest_chapter">Latest chapter</string>
 | 
			
		||||
    <string name="action_sort_chapter_fetch_date">Chapter fetch date</string>
 | 
			
		||||
    <string name="action_sort_date_added">Date added</string>
 | 
			
		||||
@@ -255,6 +262,16 @@
 | 
			
		||||
    <string name="pref_update_only_non_completed">With \"Completed\" status</string>
 | 
			
		||||
    <string name="pref_update_only_started">That haven\'t been started</string>
 | 
			
		||||
    <string name="pref_library_update_show_tab_badge">Show unread count on Updates icon</string>
 | 
			
		||||
    <string name="pref_update_only_in_release_period">Outside release period</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pref_update_release_grace_period">Grace release period:</string>
 | 
			
		||||
    <string name="pref_update_release_leading_days">Check %s day(s) before</string>
 | 
			
		||||
    <string name="pref_update_release_following_days">Check %s day(s) after</string>
 | 
			
		||||
    <string name="pref_update_release_grace_period_info1">It is recommended to keep small grace period to minimize stress on servers.</string>
 | 
			
		||||
    <string name="pref_update_release_grace_period_info2">The more checks comic missed, the longer extend check interval (max at 28 day).</string>
 | 
			
		||||
    <string name="pref_update_release_grace_period_info3">It is recommend to remove or migrate source if comic in Dropped status filter.</string>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <string name="pref_library_update_refresh_metadata">Automatically refresh metadata</string>
 | 
			
		||||
    <string name="pref_library_update_refresh_metadata_summary">Check for new cover and details when updating library</string>
 | 
			
		||||
    <string name="pref_library_update_refresh_trackers">Automatically refresh trackers</string>
 | 
			
		||||
@@ -593,6 +610,7 @@
 | 
			
		||||
    <string name="updating_category">Updating category</string>
 | 
			
		||||
    <string name="manga_from_library">From library</string>
 | 
			
		||||
    <string name="downloaded_chapters">Downloaded chapters</string>
 | 
			
		||||
    <string name="intervals_header">Intervals</string>
 | 
			
		||||
    <!-- For badges/buttons on library covers. -->
 | 
			
		||||
    <string name="overlay_header">Overlay</string>
 | 
			
		||||
    <string name="tabs_header">Tabs</string>
 | 
			
		||||
@@ -618,6 +636,10 @@
 | 
			
		||||
    <string name="local_invalid_format">Invalid chapter format</string>
 | 
			
		||||
    <string name="local_filter_order_by">Order by</string>
 | 
			
		||||
    <string name="date">Date</string>
 | 
			
		||||
    <plurals name="day">
 | 
			
		||||
        <item quantity="one">1 day</item>
 | 
			
		||||
        <item quantity="other">%d days</item>
 | 
			
		||||
    </plurals>
 | 
			
		||||
 | 
			
		||||
    <!-- Manga info -->
 | 
			
		||||
    <plurals name="missing_chapters">
 | 
			
		||||
@@ -657,6 +679,10 @@
 | 
			
		||||
 | 
			
		||||
    <!-- Manga chapters -->
 | 
			
		||||
    <string name="display_mode_chapter">Chapter %1$s</string>
 | 
			
		||||
    <string name="manga_display_interval_title">Estimate every</string>
 | 
			
		||||
    <string name="manga_display_modified_interval_title">Set to update every</string>
 | 
			
		||||
    <string name="manga_modify_interval_title">Modify interval</string>
 | 
			
		||||
    <string name="manga_modify_calculated_interval_title">Customize Interval</string>
 | 
			
		||||
    <string name="chapter_downloading_progress">Downloading (%1$d/%2$d)</string>
 | 
			
		||||
    <string name="chapter_error">Error</string>
 | 
			
		||||
    <string name="chapter_paused">Paused</string>
 | 
			
		||||
@@ -855,6 +881,7 @@
 | 
			
		||||
    <string name="skipped_reason_not_caught_up">Skipped because there are unread chapters</string>
 | 
			
		||||
    <string name="skipped_reason_not_started">Skipped because no chapters are read</string>
 | 
			
		||||
    <string name="skipped_reason_not_always_update">Skipped because series does not require updates</string>
 | 
			
		||||
    <string name="skipped_reason_not_in_release_period">Skipped because no release was expected today</string>
 | 
			
		||||
 | 
			
		||||
    <!-- File Picker Titles -->
 | 
			
		||||
    <string name="file_select_cover">Select cover image</string>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user