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:
parent
a458bd9fdb
commit
c90f344910
5 changed files with 302 additions and 5 deletions
|
@ -3,12 +3,16 @@ package eu.kanade.domain.manga.interactor
|
||||||
import eu.kanade.domain.manga.model.hasCustomCover
|
import eu.kanade.domain.manga.model.hasCustomCover
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
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.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class UpdateManga(
|
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 {
|
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
||||||
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Date().time))
|
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Date().time))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
package eu.kanade.presentation.more.settings.screen
|
package eu.kanade.presentation.more.settings.screen
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
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.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
@ -10,9 +17,14 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
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.platform.LocalContext
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
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.compose.ui.util.fastMap
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
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_HAS_UNREAD
|
||||||
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.presentation.core.components.WheelPickerDefaults
|
||||||
|
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
@ -132,8 +147,8 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||||
|
|
||||||
val included by libraryUpdateCategoriesPref.collectAsState()
|
val included by libraryUpdateCategoriesPref.collectAsState()
|
||||||
val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
|
val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
|
||||||
var showDialog by rememberSaveable { mutableStateOf(false) }
|
var showCategoriesDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
if (showDialog) {
|
if (showCategoriesDialog) {
|
||||||
TriStateListDialog(
|
TriStateListDialog(
|
||||||
title = stringResource(R.string.categories),
|
title = stringResource(R.string.categories),
|
||||||
message = stringResource(R.string.pref_library_update_categories_details),
|
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 } },
|
initialChecked = included.mapNotNull { id -> allCategories.find { it.id.toString() == id } },
|
||||||
initialInversed = excluded.mapNotNull { id -> allCategories.find { it.id.toString() == id } },
|
initialInversed = excluded.mapNotNull { id -> allCategories.find { it.id.toString() == id } },
|
||||||
itemLabel = { it.visualName },
|
itemLabel = { it.visualName },
|
||||||
onDismissRequest = { showDialog = false },
|
onDismissRequest = { showCategoriesDialog = false },
|
||||||
onValueChanged = { newIncluded, newExcluded ->
|
onValueChanged = { newIncluded, newExcluded ->
|
||||||
libraryUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
|
libraryUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
|
||||||
libraryUpdateCategoriesExcludePref.set(newExcluded.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_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
|
||||||
MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
|
MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
|
||||||
MANGA_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
|
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(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.categories),
|
title = stringResource(R.string.categories),
|
||||||
subtitle = getCategoriesLabel(
|
subtitle = getCategoriesLabel(
|
||||||
|
@ -201,7 +251,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||||
included = included,
|
included = included,
|
||||||
excluded = excluded,
|
excluded = excluded,
|
||||||
),
|
),
|
||||||
onClick = { showDialog = true },
|
onClick = { showCategoriesDialog = true },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = libraryPreferences.autoUpdateMetadata(),
|
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_HAS_UNREAD,
|
||||||
MANGA_NON_COMPLETED,
|
MANGA_NON_COMPLETED,
|
||||||
MANGA_NON_READ,
|
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 autoUpdateMetadata() = preferenceStore.getBoolean("auto_update_metadata", false)
|
||||||
|
|
||||||
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", 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 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)
|
fun filterTracking(id: Int) = preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriStateFilter.DISABLED)
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -142,5 +156,6 @@ class LibraryPreferences(
|
||||||
const val MANGA_NON_COMPLETED = "manga_ongoing"
|
const val MANGA_NON_COMPLETED = "manga_ongoing"
|
||||||
const val MANGA_HAS_UNREAD = "manga_fully_read"
|
const val MANGA_HAS_UNREAD = "manga_fully_read"
|
||||||
const val MANGA_NON_READ = "manga_started"
|
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_settings">Settings</string>
|
||||||
<string name="action_menu">Menu</string>
|
<string name="action_menu">Menu</string>
|
||||||
<string name="action_filter">Filter</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_bookmarked">Bookmarked</string>
|
||||||
<string name="action_filter_tracked">Tracked</string>
|
<string name="action_filter_tracked">Tracked</string>
|
||||||
<string name="action_filter_unread">Unread</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 -->
|
<!-- reserved for #4048 -->
|
||||||
<string name="action_filter_empty">Remove filter</string>
|
<string name="action_filter_empty">Remove filter</string>
|
||||||
<string name="action_sort_alpha">Alphabetically</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_read">Last read</string>
|
||||||
<string name="action_sort_last_manga_update">Last update check</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_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_latest_chapter">Latest chapter</string>
|
||||||
<string name="action_sort_chapter_fetch_date">Chapter fetch date</string>
|
<string name="action_sort_chapter_fetch_date">Chapter fetch date</string>
|
||||||
<string name="action_sort_date_added">Date added</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_non_completed">With \"Completed\" status</string>
|
||||||
<string name="pref_update_only_started">That haven\'t been started</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_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">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_metadata_summary">Check for new cover and details when updating library</string>
|
||||||
<string name="pref_library_update_refresh_trackers">Automatically refresh trackers</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="updating_category">Updating category</string>
|
||||||
<string name="manga_from_library">From library</string>
|
<string name="manga_from_library">From library</string>
|
||||||
<string name="downloaded_chapters">Downloaded chapters</string>
|
<string name="downloaded_chapters">Downloaded chapters</string>
|
||||||
|
<string name="intervals_header">Intervals</string>
|
||||||
<!-- For badges/buttons on library covers. -->
|
<!-- For badges/buttons on library covers. -->
|
||||||
<string name="overlay_header">Overlay</string>
|
<string name="overlay_header">Overlay</string>
|
||||||
<string name="tabs_header">Tabs</string>
|
<string name="tabs_header">Tabs</string>
|
||||||
|
@ -618,6 +636,10 @@
|
||||||
<string name="local_invalid_format">Invalid chapter format</string>
|
<string name="local_invalid_format">Invalid chapter format</string>
|
||||||
<string name="local_filter_order_by">Order by</string>
|
<string name="local_filter_order_by">Order by</string>
|
||||||
<string name="date">Date</string>
|
<string name="date">Date</string>
|
||||||
|
<plurals name="day">
|
||||||
|
<item quantity="one">1 day</item>
|
||||||
|
<item quantity="other">%d days</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
<!-- Manga info -->
|
<!-- Manga info -->
|
||||||
<plurals name="missing_chapters">
|
<plurals name="missing_chapters">
|
||||||
|
@ -657,6 +679,10 @@
|
||||||
|
|
||||||
<!-- Manga chapters -->
|
<!-- Manga chapters -->
|
||||||
<string name="display_mode_chapter">Chapter %1$s</string>
|
<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_downloading_progress">Downloading (%1$d/%2$d)</string>
|
||||||
<string name="chapter_error">Error</string>
|
<string name="chapter_error">Error</string>
|
||||||
<string name="chapter_paused">Paused</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_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_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_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 -->
|
<!-- File Picker Titles -->
|
||||||
<string name="file_select_cover">Select cover image</string>
|
<string name="file_select_cover">Select cover image</string>
|
||||||
|
|
Reference in a new issue