mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Change fetch interval action to show days until next expected update
This commit is contained in:
		@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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),
 | 
			
		||||
 
 | 
			
		||||
@@ -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 },
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -275,12 +275,12 @@
 | 
			
		||||
    <string name="charging">When charging</string>
 | 
			
		||||
    <string name="restrictions">Restrictions: %s</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pref_library_update_manga_restriction">Skip updating entries</string>
 | 
			
		||||
    <string name="pref_update_only_completely_read">With unread chapter(s)</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_library_update_smart_update">Smart update</string>
 | 
			
		||||
    <string name="pref_update_only_completely_read">Skip entries with unread chapter(s)</string>
 | 
			
		||||
    <string name="pref_update_only_non_completed">Skip entries with \"Completed\" status</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_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_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="manga_display_interval_title">Estimate 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" -->
 | 
			
		||||
    <string name="manga_interval_expected_update">Next update expected in around %s</string>
 | 
			
		||||
    <string name="manga_modify_calculated_interval_title">Customize interval</string>
 | 
			
		||||
    <string name="manga_interval_expected_update">Next update expected in around %1$s, checking around every %2$s</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_error">Error</string>
 | 
			
		||||
    <string name="chapter_paused">Paused</string>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user