diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt
index 7a6d8d50b..444af9aec 100644
--- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt
@@ -78,12 +78,13 @@ import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrollingUp
import tachiyomi.source.local.isLocal
+import java.time.Instant
@Composable
fun MangaScreen(
state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState,
- fetchInterval: Int?,
+ nextUpdate: Instant?,
isTabletUi: Boolean,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
@@ -138,7 +139,7 @@ fun MangaScreen(
MangaScreenSmallImpl(
state = state,
snackbarHostState = snackbarHostState,
- fetchInterval = fetchInterval,
+ nextUpdate = nextUpdate,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onBackClicked = onBackClicked,
@@ -175,7 +176,7 @@ fun MangaScreen(
snackbarHostState = snackbarHostState,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
- fetchInterval = fetchInterval,
+ nextUpdate = nextUpdate,
onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter,
@@ -211,7 +212,7 @@ fun MangaScreen(
private fun MangaScreenSmallImpl(
state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState,
- fetchInterval: Int?,
+ nextUpdate: Instant?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit,
@@ -402,7 +403,7 @@ private fun MangaScreenSmallImpl(
MangaActionRow(
favorite = state.manga.favorite,
trackingCount = state.trackingCount,
- fetchInterval = fetchInterval,
+ nextUpdate = nextUpdate,
isUserIntervalMode = state.manga.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked,
@@ -462,7 +463,7 @@ private fun MangaScreenSmallImpl(
fun MangaScreenLargeImpl(
state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState,
- fetchInterval: Int?,
+ nextUpdate: Instant?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit,
@@ -641,7 +642,7 @@ fun MangaScreenLargeImpl(
MangaActionRow(
favorite = state.manga.favorite,
trackingCount = state.trackingCount,
- fetchInterval = fetchInterval,
+ nextUpdate = nextUpdate,
isUserIntervalMode = state.manga.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked,
diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt
index f59b4574a..ace822cd1 100644
--- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt
+++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt
@@ -2,8 +2,11 @@ package eu.kanade.presentation.manga.components
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
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
@@ -20,6 +23,7 @@ import kotlinx.collections.immutable.toImmutableList
import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.WheelTextPicker
+import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
import java.time.Instant
@@ -59,57 +63,71 @@ fun DeleteChaptersDialog(
@Composable
fun SetIntervalDialog(
interval: Int,
- nextUpdate: Long,
+ nextUpdate: Instant?,
onDismissRequest: () -> Unit,
- onValueChanged: (Int) -> Unit,
+ onValueChanged: ((Int) -> Unit)? = null,
) {
var selectedInterval by rememberSaveable { mutableIntStateOf(if (interval < 0) -interval else 0) }
val nextUpdateDays = remember(nextUpdate) {
- val now = Instant.now()
- val nextUpdateInstant = Instant.ofEpochMilli(nextUpdate)
-
- now.until(nextUpdateInstant, ChronoUnit.DAYS)
+ return@remember if (nextUpdate != null) {
+ val now = Instant.now()
+ now.until(nextUpdate, ChronoUnit.DAYS).toInt()
+ } else {
+ null
+ }
}
+ // TODO: selecting "1" then doesn't allow for future changes unless defaulting first?
AlertDialog(
onDismissRequest = onDismissRequest,
- title = { Text(stringResource(MR.strings.manga_modify_calculated_interval_title)) },
+ title = { Text(stringResource(MR.strings.pref_library_update_smart_update)) },
text = {
Column {
- if (nextUpdateDays >= 0) {
+ if (nextUpdateDays != null && nextUpdateDays >= 0) {
Text(
stringResource(
MR.strings.manga_interval_expected_update,
pluralStringResource(
MR.plurals.day,
- count = nextUpdateDays.toInt(),
+ count = nextUpdateDays,
nextUpdateDays,
),
+ pluralStringResource(
+ MR.plurals.day,
+ count = interval,
+ interval,
+ ),
),
)
+
+ Spacer(Modifier.height(MaterialTheme.padding.small))
}
- BoxWithConstraints(
- modifier = Modifier.fillMaxWidth(),
- contentAlignment = Alignment.Center,
- ) {
- val size = DpSize(width = maxWidth / 2, height = 128.dp)
- val items = (0..FetchInterval.MAX_INTERVAL)
- .map {
- if (it == 0) {
- stringResource(MR.strings.label_default)
- } else {
- it.toString()
+ if (onValueChanged != null) {
+ Text(stringResource(MR.strings.manga_interval_custom_amount))
+
+ BoxWithConstraints(
+ modifier = Modifier.fillMaxWidth(),
+ contentAlignment = Alignment.Center,
+ ) {
+ val size = DpSize(width = maxWidth / 2, height = 128.dp)
+ val items = (0..FetchInterval.MAX_INTERVAL)
+ .map {
+ if (it == 0) {
+ stringResource(MR.strings.label_default)
+ } else {
+ it.toString()
+ }
}
- }
- .toImmutableList()
- WheelTextPicker(
- items = items,
- size = size,
- startIndex = selectedInterval,
- onSelectionChanged = { selectedInterval = it },
- )
+ .toImmutableList()
+ WheelTextPicker(
+ items = items,
+ size = size,
+ startIndex = selectedInterval,
+ onSelectionChanged = { selectedInterval = it },
+ )
+ }
}
}
},
@@ -120,7 +138,7 @@ fun SetIntervalDialog(
},
confirmButton = {
TextButton(onClick = {
- onValueChanged(selectedInterval)
+ onValueChanged?.invoke(selectedInterval)
onDismissRequest()
}) {
Text(text = stringResource(MR.strings.action_ok))
diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt
index 7b5de2467..283f71bbb 100644
--- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt
+++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt
@@ -86,7 +86,8 @@ import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.clickableNoIndication
import tachiyomi.presentation.core.util.secondaryItemAlpha
-import kotlin.math.absoluteValue
+import java.time.Instant
+import java.time.temporal.ChronoUnit
import kotlin.math.roundToInt
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
@@ -165,7 +166,7 @@ fun MangaInfoBox(
fun MangaActionRow(
favorite: Boolean,
trackingCount: Int,
- fetchInterval: Int?,
+ nextUpdate: Instant?,
isUserIntervalMode: Boolean,
onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?,
@@ -177,6 +178,16 @@ fun MangaActionRow(
) {
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
+ // TODO: show something better when using custom interval
+ val nextUpdateDays = remember(nextUpdate) {
+ return@remember if (nextUpdate != null) {
+ val now = Instant.now()
+ now.until(nextUpdate, ChronoUnit.DAYS).toInt()
+ } else {
+ null
+ }
+ }
+
Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
MangaActionButton(
title = if (favorite) {
@@ -189,18 +200,20 @@ fun MangaActionRow(
onClick = onAddToLibraryClicked,
onLongClick = onEditCategory,
)
- if (onEditIntervalClicked != null && fetchInterval != null) {
- MangaActionButton(
- title = pluralStringResource(
+ MangaActionButton(
+ title = if (nextUpdateDays != null) {
+ pluralStringResource(
MR.plurals.day,
- count = fetchInterval.absoluteValue,
- fetchInterval.absoluteValue,
- ),
- icon = Icons.Default.HourglassEmpty,
- color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
- onClick = onEditIntervalClicked,
- )
- }
+ count = nextUpdateDays,
+ nextUpdateDays,
+ )
+ } else {
+ stringResource(MR.strings.not_applicable)
+ },
+ icon = Icons.Default.HourglassEmpty,
+ color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
+ onClick = { onEditIntervalClicked?.invoke() },
+ )
MangaActionButton(
title = if (trackingCount == 0) {
stringResource(MR.strings.manga_tracking_tab)
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 1ad7410be..346a60b86 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
@@ -198,7 +198,7 @@ object SettingsLibraryScreen : SearchableSettings {
),
Preference.PreferenceItem.MultiSelectListPreference(
pref = libraryPreferences.autoUpdateMangaRestrictions(),
- title = stringResource(MR.strings.pref_library_update_manga_restriction),
+ title = stringResource(MR.strings.pref_library_update_smart_update),
entries = persistentMapOf(
MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read),
MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started),
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt
index f4c7c48b6..b73b4bcc3 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt
@@ -104,7 +104,7 @@ class MangaScreen(
MangaScreen(
state = successState,
snackbarHostState = screenModel.snackbarHostState,
- fetchInterval = successState.manga.fetchInterval,
+ nextUpdate = successState.manga.expectedNextUpdate,
isTabletUi = isTabletUi(),
chapterSwipeStartAction = screenModel.chapterSwipeStartAction,
chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
@@ -146,7 +146,7 @@ class MangaScreen(
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.manga.favorite },
onEditFetchIntervalClicked = screenModel::showSetFetchIntervalDialog.takeIf {
- screenModel.isUpdateIntervalEnabled && successState.manga.favorite
+ successState.manga.favorite
},
onMigrateClicked = {
navigator.push(MigrateSearchScreen(successState.manga.id))
@@ -243,9 +243,10 @@ class MangaScreen(
is MangaScreenModel.Dialog.SetFetchInterval -> {
SetIntervalDialog(
interval = dialog.manga.fetchInterval,
- nextUpdate = dialog.manga.nextUpdate,
+ nextUpdate = dialog.manga.expectedNextUpdate,
onDismissRequest = onDismissRequest,
- onValueChanged = { screenModel.setFetchInterval(dialog.manga, it) },
+ onValueChanged = { interval: Int -> screenModel.setFetchInterval(dialog.manga, interval) }
+ .takeIf { screenModel.isUpdateIntervalEnabled },
)
}
}
diff --git a/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt b/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt
index f694355a4..2b99c29bf 100644
--- a/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt
+++ b/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt
@@ -1,8 +1,10 @@
package tachiyomi.domain.manga.model
+import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import tachiyomi.core.preference.TriState
import java.io.Serializable
+import java.time.Instant
data class Manga(
val id: Long,
@@ -29,6 +31,11 @@ data class Manga(
val favoriteModifiedAt: Long?,
) : Serializable {
+ val expectedNextUpdate: Instant?
+ get() = nextUpdate
+ .takeIf { status != SManga.COMPLETED.toLong() }
+ ?.let { Instant.ofEpochMilli(it) }
+
val sorting: Long
get() = chapterFlags and CHAPTER_SORTING_MASK
diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml
index e130180c1..df8d826a6 100644
--- a/i18n/src/commonMain/resources/MR/base/strings.xml
+++ b/i18n/src/commonMain/resources/MR/base/strings.xml
@@ -275,12 +275,12 @@
When charging
Restrictions: %s
- Skip updating entries
- With unread chapter(s)
- With \"Completed\" status
- That haven\'t been started
+ Smart update
+ Skip entries with unread chapter(s)
+ Skip entries with \"Completed\" status
+ Skip unstarted entries
+ Predict next release time
Show unread count on Updates icon
- Outside expected release period
Automatically refresh metadata
Check for new cover and details when updating library
@@ -668,9 +668,10 @@
Chapter %1$s
Estimate every
Set to update every
+ Next update
- Next update expected in around %s
- Customize interval
+ Next update expected in around %1$s, checking around every %2$s
+ Custom update frequency:
Downloading (%1$d/%2$d)
Error
Paused