Change fetch interval action to show days until next expected update

This commit is contained in:
arkon 2024-01-05 17:08:39 -05:00
parent e0a0942015
commit 32bed9b041
7 changed files with 102 additions and 61 deletions

View file

@ -78,12 +78,13 @@ import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrollingUp import tachiyomi.presentation.core.util.isScrollingUp
import tachiyomi.source.local.isLocal import tachiyomi.source.local.isLocal
import java.time.Instant
@Composable @Composable
fun MangaScreen( fun MangaScreen(
state: MangaScreenModel.State.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
fetchInterval: Int?, nextUpdate: Instant?,
isTabletUi: Boolean, isTabletUi: Boolean,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
@ -138,7 +139,7 @@ fun MangaScreen(
MangaScreenSmallImpl( MangaScreenSmallImpl(
state = state, state = state,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
fetchInterval = fetchInterval, nextUpdate = nextUpdate,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
@ -175,7 +176,7 @@ fun MangaScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
fetchInterval = fetchInterval, nextUpdate = nextUpdate,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter, onDownloadChapter = onDownloadChapter,
@ -211,7 +212,7 @@ fun MangaScreen(
private fun MangaScreenSmallImpl( private fun MangaScreenSmallImpl(
state: MangaScreenModel.State.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
fetchInterval: Int?, nextUpdate: Instant?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
@ -402,7 +403,7 @@ private fun MangaScreenSmallImpl(
MangaActionRow( MangaActionRow(
favorite = state.manga.favorite, favorite = state.manga.favorite,
trackingCount = state.trackingCount, trackingCount = state.trackingCount,
fetchInterval = fetchInterval, nextUpdate = nextUpdate,
isUserIntervalMode = state.manga.fetchInterval < 0, isUserIntervalMode = state.manga.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked, onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked, onWebViewClicked = onWebViewClicked,
@ -462,7 +463,7 @@ private fun MangaScreenSmallImpl(
fun MangaScreenLargeImpl( fun MangaScreenLargeImpl(
state: MangaScreenModel.State.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
fetchInterval: Int?, nextUpdate: Instant?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
@ -641,7 +642,7 @@ fun MangaScreenLargeImpl(
MangaActionRow( MangaActionRow(
favorite = state.manga.favorite, favorite = state.manga.favorite,
trackingCount = state.trackingCount, trackingCount = state.trackingCount,
fetchInterval = fetchInterval, nextUpdate = nextUpdate,
isUserIntervalMode = state.manga.fetchInterval < 0, isUserIntervalMode = state.manga.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked, onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked, onWebViewClicked = onWebViewClicked,

View file

@ -2,8 +2,11 @@ package eu.kanade.presentation.manga.components
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -20,6 +23,7 @@ import kotlinx.collections.immutable.toImmutableList
import tachiyomi.domain.manga.interactor.FetchInterval import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.WheelTextPicker import tachiyomi.presentation.core.components.WheelTextPicker
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import java.time.Instant import java.time.Instant
@ -59,57 +63,71 @@ fun DeleteChaptersDialog(
@Composable @Composable
fun SetIntervalDialog( fun SetIntervalDialog(
interval: Int, interval: Int,
nextUpdate: Long, nextUpdate: Instant?,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onValueChanged: (Int) -> Unit, onValueChanged: ((Int) -> Unit)? = null,
) { ) {
var selectedInterval by rememberSaveable { mutableIntStateOf(if (interval < 0) -interval else 0) } var selectedInterval by rememberSaveable { mutableIntStateOf(if (interval < 0) -interval else 0) }
val nextUpdateDays = remember(nextUpdate) { val nextUpdateDays = remember(nextUpdate) {
val now = Instant.now() return@remember if (nextUpdate != null) {
val nextUpdateInstant = Instant.ofEpochMilli(nextUpdate) val now = Instant.now()
now.until(nextUpdate, ChronoUnit.DAYS).toInt()
now.until(nextUpdateInstant, ChronoUnit.DAYS) } else {
null
}
} }
// TODO: selecting "1" then doesn't allow for future changes unless defaulting first?
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = { Text(stringResource(MR.strings.manga_modify_calculated_interval_title)) }, title = { Text(stringResource(MR.strings.pref_library_update_smart_update)) },
text = { text = {
Column { Column {
if (nextUpdateDays >= 0) { if (nextUpdateDays != null && nextUpdateDays >= 0) {
Text( Text(
stringResource( stringResource(
MR.strings.manga_interval_expected_update, MR.strings.manga_interval_expected_update,
pluralStringResource( pluralStringResource(
MR.plurals.day, MR.plurals.day,
count = nextUpdateDays.toInt(), count = nextUpdateDays,
nextUpdateDays, nextUpdateDays,
), ),
pluralStringResource(
MR.plurals.day,
count = interval,
interval,
),
), ),
) )
Spacer(Modifier.height(MaterialTheme.padding.small))
} }
BoxWithConstraints( if (onValueChanged != null) {
modifier = Modifier.fillMaxWidth(), Text(stringResource(MR.strings.manga_interval_custom_amount))
contentAlignment = Alignment.Center,
) { BoxWithConstraints(
val size = DpSize(width = maxWidth / 2, height = 128.dp) modifier = Modifier.fillMaxWidth(),
val items = (0..FetchInterval.MAX_INTERVAL) contentAlignment = Alignment.Center,
.map { ) {
if (it == 0) { val size = DpSize(width = maxWidth / 2, height = 128.dp)
stringResource(MR.strings.label_default) val items = (0..FetchInterval.MAX_INTERVAL)
} else { .map {
it.toString() if (it == 0) {
stringResource(MR.strings.label_default)
} else {
it.toString()
}
} }
} .toImmutableList()
.toImmutableList() WheelTextPicker(
WheelTextPicker( items = items,
items = items, size = size,
size = size, startIndex = selectedInterval,
startIndex = selectedInterval, onSelectionChanged = { selectedInterval = it },
onSelectionChanged = { selectedInterval = it }, )
) }
} }
} }
}, },
@ -120,7 +138,7 @@ fun SetIntervalDialog(
}, },
confirmButton = { confirmButton = {
TextButton(onClick = { TextButton(onClick = {
onValueChanged(selectedInterval) onValueChanged?.invoke(selectedInterval)
onDismissRequest() onDismissRequest()
}) { }) {
Text(text = stringResource(MR.strings.action_ok)) Text(text = stringResource(MR.strings.action_ok))

View file

@ -86,7 +86,8 @@ import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.clickableNoIndication import tachiyomi.presentation.core.util.clickableNoIndication
import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.secondaryItemAlpha
import kotlin.math.absoluteValue import java.time.Instant
import java.time.temporal.ChronoUnit
import kotlin.math.roundToInt import kotlin.math.roundToInt
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE)) private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
@ -165,7 +166,7 @@ fun MangaInfoBox(
fun MangaActionRow( fun MangaActionRow(
favorite: Boolean, favorite: Boolean,
trackingCount: Int, trackingCount: Int,
fetchInterval: Int?, nextUpdate: Instant?,
isUserIntervalMode: Boolean, isUserIntervalMode: Boolean,
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
@ -177,6 +178,16 @@ fun MangaActionRow(
) { ) {
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f) 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)) { Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
MangaActionButton( MangaActionButton(
title = if (favorite) { title = if (favorite) {
@ -189,18 +200,20 @@ fun MangaActionRow(
onClick = onAddToLibraryClicked, onClick = onAddToLibraryClicked,
onLongClick = onEditCategory, onLongClick = onEditCategory,
) )
if (onEditIntervalClicked != null && fetchInterval != null) { MangaActionButton(
MangaActionButton( title = if (nextUpdateDays != null) {
title = pluralStringResource( pluralStringResource(
MR.plurals.day, MR.plurals.day,
count = fetchInterval.absoluteValue, count = nextUpdateDays,
fetchInterval.absoluteValue, nextUpdateDays,
), )
icon = Icons.Default.HourglassEmpty, } else {
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor, stringResource(MR.strings.not_applicable)
onClick = onEditIntervalClicked, },
) icon = Icons.Default.HourglassEmpty,
} color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
onClick = { onEditIntervalClicked?.invoke() },
)
MangaActionButton( MangaActionButton(
title = if (trackingCount == 0) { title = if (trackingCount == 0) {
stringResource(MR.strings.manga_tracking_tab) stringResource(MR.strings.manga_tracking_tab)

View file

@ -198,7 +198,7 @@ object SettingsLibraryScreen : SearchableSettings {
), ),
Preference.PreferenceItem.MultiSelectListPreference( Preference.PreferenceItem.MultiSelectListPreference(
pref = libraryPreferences.autoUpdateMangaRestrictions(), pref = libraryPreferences.autoUpdateMangaRestrictions(),
title = stringResource(MR.strings.pref_library_update_manga_restriction), title = stringResource(MR.strings.pref_library_update_smart_update),
entries = persistentMapOf( entries = persistentMapOf(
MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read), MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read),
MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started), MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started),

View file

@ -104,7 +104,7 @@ class MangaScreen(
MangaScreen( MangaScreen(
state = successState, state = successState,
snackbarHostState = screenModel.snackbarHostState, snackbarHostState = screenModel.snackbarHostState,
fetchInterval = successState.manga.fetchInterval, nextUpdate = successState.manga.expectedNextUpdate,
isTabletUi = isTabletUi(), isTabletUi = isTabletUi(),
chapterSwipeStartAction = screenModel.chapterSwipeStartAction, chapterSwipeStartAction = screenModel.chapterSwipeStartAction,
chapterSwipeEndAction = screenModel.chapterSwipeEndAction, chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
@ -146,7 +146,7 @@ class MangaScreen(
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() }, onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.manga.favorite }, onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.manga.favorite },
onEditFetchIntervalClicked = screenModel::showSetFetchIntervalDialog.takeIf { onEditFetchIntervalClicked = screenModel::showSetFetchIntervalDialog.takeIf {
screenModel.isUpdateIntervalEnabled && successState.manga.favorite successState.manga.favorite
}, },
onMigrateClicked = { onMigrateClicked = {
navigator.push(MigrateSearchScreen(successState.manga.id)) navigator.push(MigrateSearchScreen(successState.manga.id))
@ -243,9 +243,10 @@ class MangaScreen(
is MangaScreenModel.Dialog.SetFetchInterval -> { is MangaScreenModel.Dialog.SetFetchInterval -> {
SetIntervalDialog( SetIntervalDialog(
interval = dialog.manga.fetchInterval, interval = dialog.manga.fetchInterval,
nextUpdate = dialog.manga.nextUpdate, nextUpdate = dialog.manga.expectedNextUpdate,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onValueChanged = { screenModel.setFetchInterval(dialog.manga, it) }, onValueChanged = { interval: Int -> screenModel.setFetchInterval(dialog.manga, interval) }
.takeIf { screenModel.isUpdateIntervalEnabled },
) )
} }
} }

View file

@ -1,8 +1,10 @@
package tachiyomi.domain.manga.model package tachiyomi.domain.manga.model
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import tachiyomi.core.preference.TriState import tachiyomi.core.preference.TriState
import java.io.Serializable import java.io.Serializable
import java.time.Instant
data class Manga( data class Manga(
val id: Long, val id: Long,
@ -29,6 +31,11 @@ data class Manga(
val favoriteModifiedAt: Long?, val favoriteModifiedAt: Long?,
) : Serializable { ) : Serializable {
val expectedNextUpdate: Instant?
get() = nextUpdate
.takeIf { status != SManga.COMPLETED.toLong() }
?.let { Instant.ofEpochMilli(it) }
val sorting: Long val sorting: Long
get() = chapterFlags and CHAPTER_SORTING_MASK get() = chapterFlags and CHAPTER_SORTING_MASK

View file

@ -275,12 +275,12 @@
<string name="charging">When charging</string> <string name="charging">When charging</string>
<string name="restrictions">Restrictions: %s</string> <string name="restrictions">Restrictions: %s</string>
<string name="pref_library_update_manga_restriction">Skip updating entries</string> <string name="pref_library_update_smart_update">Smart update</string>
<string name="pref_update_only_completely_read">With unread chapter(s)</string> <string name="pref_update_only_completely_read">Skip entries with unread chapter(s)</string>
<string name="pref_update_only_non_completed">With \"Completed\" status</string> <string name="pref_update_only_non_completed">Skip entries with \"Completed\" status</string>
<string name="pref_update_only_started">That haven\'t been started</string> <string name="pref_update_only_started">Skip unstarted entries</string>
<string name="pref_update_only_in_release_period">Predict next release time</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 expected release period</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>
@ -668,9 +668,10 @@
<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_interval_title">Estimate every</string>
<string name="manga_display_modified_interval_title">Set to update every</string> <string name="manga_display_modified_interval_title">Set to update every</string>
<string name="manga_interval_header">Next update</string>
<!-- "... around 2 days" --> <!-- "... around 2 days" -->
<string name="manga_interval_expected_update">Next update expected in around %s</string> <string name="manga_interval_expected_update">Next update expected in around %1$s, checking around every %2$s</string>
<string name="manga_modify_calculated_interval_title">Customize interval</string> <string name="manga_interval_custom_amount">Custom update frequency:</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>