diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt index 675b2e21bb..b50e4ddb52 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt @@ -1,7 +1,9 @@ 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 @@ -12,6 +14,7 @@ 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 @@ -52,7 +55,7 @@ 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.presentation.core.components.WheelPickerDefaults +import tachiyomi.domain.manga.interactor.MAX_GRACE_PERIOD import tachiyomi.presentation.core.components.WheelTextPicker import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -144,6 +147,7 @@ object SettingsLibraryScreen : SearchableSettings { val libraryUpdateCategoriesExcludePref = libraryPreferences.libraryUpdateCategoriesExclude() val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState() + val libraryUpdateMangaRestriction by libraryUpdateMangaRestrictionPref.collectAsState() val included by libraryUpdateCategoriesPref.collectAsState() val excluded by libraryUpdateCategoriesExcludePref.collectAsState() @@ -182,7 +186,7 @@ object SettingsLibraryScreen : SearchableSettings { } return Preference.PreferenceGroup( title = stringResource(R.string.pref_category_library_update), - preferenceItems = listOf( + preferenceItems = listOfNotNull( Preference.PreferenceItem.ListPreference( pref = libraryUpdateIntervalPref, title = stringResource(R.string.pref_library_update_interval), @@ -216,34 +220,6 @@ object SettingsLibraryScreen : SearchableSettings { true }, ), - Preference.PreferenceItem.MultiSelectListPreference( - pref = libraryUpdateMangaRestrictionPref, - title = stringResource(R.string.pref_library_update_manga_restriction), - entries = mapOf( - 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( @@ -264,6 +240,27 @@ object SettingsLibraryScreen : SearchableSettings { title = stringResource(R.string.pref_library_update_refresh_trackers), subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary), ), + Preference.PreferenceItem.MultiSelectListPreference( + pref = libraryUpdateMangaRestrictionPref, + title = stringResource(R.string.pref_library_update_manga_restriction), + entries = mapOf( + 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_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 }, ), ) } @@ -306,45 +303,48 @@ object SettingsLibraryScreen : SearchableSettings { onDismissRequest: () -> Unit, onValueChanged: (portrait: Int, landscape: Int) -> Unit, ) { - val context = LocalContext.current - var leadValue by rememberSaveable { mutableStateOf(initialLead) } - var followValue by rememberSaveable { mutableStateOf(initialFollow) } + 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 = { - 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, - ) + 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, ) { - WheelPickerDefaults.Background(size = DpSize(maxWidth, maxHeight)) - val size = DpSize(width = maxWidth / 2, height = 128.dp) - val items = (0..28).map { + val items = (0..MAX_GRACE_PERIOD).map { if (it == 0) { stringResource(R.string.label_default) } else { it.toString() } } - Row { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { WheelTextPicker( size = size, items = items, diff --git a/domain/src/main/java/tachiyomi/domain/manga/interactor/SetMangaUpdateInterval.kt b/domain/src/main/java/tachiyomi/domain/manga/interactor/SetMangaUpdateInterval.kt index 8a401b0285..ac2f9f91c7 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/interactor/SetMangaUpdateInterval.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/interactor/SetMangaUpdateInterval.kt @@ -11,6 +11,8 @@ import java.time.ZonedDateTime import java.time.temporal.ChronoUnit import kotlin.math.absoluteValue +const val MAX_GRACE_PERIOD = 28 + fun updateIntervalMeta( manga: Manga, chapters: List, @@ -29,41 +31,54 @@ fun updateIntervalMeta( null } else { MangaUpdate(id = manga.id, nextUpdate = nextUpdate, calculateInterval = interval) } } + fun calculateInterval(chapters: List, zonedDateTime: ZonedDateTime): Int { - val sortChapters = - chapters.sortedWith(compareBy { 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 sortedChapters = chapters + .sortedWith(compareByDescending { it.dateUpload }.thenByDescending { it.dateFetch }) + .take(50) + + val uploadDates = sortedChapters + .filter { it.dateUpload > 0L } + .map { + ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zonedDateTime.zone) + .toLocalDate() + .atStartOfDay() + } + .distinct() + val fetchDates = sortedChapters + .map { + ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zonedDateTime.zone) + .toLocalDate() + .atStartOfDay() + } + .distinct() + 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 upload date from source + uploadDates.size >= 3 -> { + val uploadDelta = uploadDates.last().until(uploadDates.first(), ChronoUnit.DAYS) + val uploadPeriod = uploadDates.indexOf(uploadDates.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() + // Enough fetch date from client + fetchDates.size >= 3 -> { + val fetchDelta = fetchDates.last().until(fetchDates.first(), ChronoUnit.DAYS) + val uploadPeriod = fetchDates.indexOf(fetchDates.last()) + fetchDelta.floorDiv(uploadPeriod).toInt() } - // default 7 days + // Default to 7 days else -> 7 } - // min 1, max 28 days - return newInterval.coerceIn(1, 28) + // Min 1, max 28 days + return newInterval.coerceIn(1, MAX_GRACE_PERIOD) } -private fun calculateNextUpdate(manga: Manga, interval: Int, zonedDateTime: ZonedDateTime, currentFetchRange: Pair): Long { +private fun calculateNextUpdate( + manga: Manga, + interval: Int, + zonedDateTime: ZonedDateTime, + currentFetchRange: Pair, +): Long { return if (manga.nextUpdate !in currentFetchRange.first.rangeTo(currentFetchRange.second + 1) || manga.calculateInterval == 0 ) { diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index ad750876c1..774f340722 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -262,15 +262,18 @@ With \"Completed\" status That haven\'t been started Show unread count on Updates icon - Outside release period - - Grace release period: - Check %s day(s) before - Check %s day(s) after - It is recommended to keep small grace period to minimize stress on servers. - The more checks comic missed, the longer extend check interval (max at 28 day). - It is recommend to remove or migrate source if comic in Dropped status filter. + Outside expected release period + Expected release grace period + + %d day before + %d days before + + + %d day after + %d days after + + A low grace period is recommended to minimize stress on sources. The more checks for an entry that are missed, the longer the interval in between checks will be with a maximum of 28 days. Automatically refresh metadata Check for new cover and details when updating library