Misc refactoring

- Abstract away relative date string building
- Dedupe large update warning logic

(cherry picked from commit 3d0d5c047228f13d8a6a7d90400f4d67f2817f24)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt
#	app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt
This commit is contained in:
arkon 2023-12-30 18:33:35 -05:00 committed by Jobobby04
parent 55115d2eb6
commit b72214b922
13 changed files with 89 additions and 143 deletions

View File

@ -0,0 +1,38 @@
package eu.kanade.presentation.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.util.lang.toRelativeString
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Date
@Composable
fun relativeDateText(
date: Long,
): String {
return relativeDateText(date = Date(date).takeIf { date > 0L })
}
@Composable
fun relativeDateText(
date: Date?,
): String {
val context = LocalContext.current
val preferences = remember { Injekt.get<UiPreferences>() }
val relativeTime = remember { preferences.relativeTime().get() }
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
return date
?.toRelativeString(
context,
relativeTime,
dateFormat,
)
?: stringResource(MR.strings.not_applicable)
}

View File

@ -1,30 +0,0 @@
package eu.kanade.presentation.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import eu.kanade.tachiyomi.util.lang.toRelativeString
import tachiyomi.presentation.core.components.ListGroupHeader
import java.text.DateFormat
import java.util.Date
@Composable
fun RelativeDateHeader(
date: Date,
relativeTime: Boolean,
dateFormat: DateFormat,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
ListGroupHeader(
modifier = modifier,
text = remember {
date.toRelativeString(
context,
relativeTime,
dateFormat,
)
},
)
}

View File

@ -8,31 +8,27 @@ import androidx.compose.material.icons.outlined.DeleteSweep
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.AppBarTitle import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.RelativeDateHeader
import eu.kanade.presentation.components.SearchToolbar import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.components.relativeDateText
import eu.kanade.presentation.history.components.HistoryItem import eu.kanade.presentation.history.components.HistoryItem
import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import tachiyomi.core.preference.InMemoryPreferenceStore
import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.history.model.HistoryWithRelations
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.FastScrollLazyColumn import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.ListGroupHeader
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.screens.LoadingScreen
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Date import java.util.Date
@Composable @Composable
@ -43,7 +39,6 @@ fun HistoryScreen(
onClickCover: (mangaId: Long) -> Unit, onClickCover: (mangaId: Long) -> Unit,
onClickResume: (mangaId: Long, chapterId: Long) -> Unit, onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit, onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
preferences: UiPreferences = Injekt.get(),
) { ) {
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
@ -89,7 +84,6 @@ fun HistoryScreen(
onClickCover = { history -> onClickCover(history.mangaId) }, onClickCover = { history -> onClickCover(history.mangaId) },
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) }, onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) }, onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
preferences = preferences,
) )
} }
} }
@ -103,11 +97,7 @@ private fun HistoryScreenContent(
onClickCover: (HistoryWithRelations) -> Unit, onClickCover: (HistoryWithRelations) -> Unit,
onClickResume: (HistoryWithRelations) -> Unit, onClickResume: (HistoryWithRelations) -> Unit,
onClickDelete: (HistoryWithRelations) -> Unit, onClickDelete: (HistoryWithRelations) -> Unit,
preferences: UiPreferences,
) { ) {
val relativeTime = remember { preferences.relativeTime().get() }
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
FastScrollLazyColumn( FastScrollLazyColumn(
contentPadding = contentPadding, contentPadding = contentPadding,
) { ) {
@ -123,11 +113,9 @@ private fun HistoryScreenContent(
) { item -> ) { item ->
when (item) { when (item) {
is HistoryUiModel.Header -> { is HistoryUiModel.Header -> {
RelativeDateHeader( ListGroupHeader(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
date = item.date, text = relativeDateText(item.date),
relativeTime = relativeTime,
dateFormat = dateFormat,
) )
} }
is HistoryUiModel.Item -> { is HistoryUiModel.Item -> {
@ -164,17 +152,6 @@ internal fun HistoryScreenPreviews(
onClickCover = {}, onClickCover = {},
onClickResume = { _, _ -> run {} }, onClickResume = { _, _ -> run {} },
onDialogChange = {}, onDialogChange = {},
preferences = UiPreferences(
InMemoryPreferenceStore(
sequenceOf(
InMemoryPreferenceStore.InMemoryPreference(
key = "relative_time_v2",
data = false,
defaultValue = false,
),
),
),
),
) )
} }
} }

View File

@ -49,6 +49,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastMap import androidx.compose.ui.util.fastMap
import eu.kanade.presentation.components.relativeDateText
import eu.kanade.presentation.manga.components.ChapterDownloadAction import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.presentation.manga.components.ChapterHeader import eu.kanade.presentation.manga.components.ChapterHeader
import eu.kanade.presentation.manga.components.ExpandableMangaDescription import eu.kanade.presentation.manga.components.ExpandableMangaDescription
@ -77,7 +78,6 @@ import eu.kanade.tachiyomi.source.online.english.Tsumino
import eu.kanade.tachiyomi.ui.manga.ChapterList import eu.kanade.tachiyomi.ui.manga.ChapterList
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
import eu.kanade.tachiyomi.ui.manga.PagePreviewState import eu.kanade.tachiyomi.ui.manga.PagePreviewState
import eu.kanade.tachiyomi.util.lang.toRelativeString
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil import exh.metadata.MetadataUtil
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
@ -105,7 +105,6 @@ 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.text.DateFormat
import java.util.Date import java.util.Date
@Composable @Composable
@ -113,8 +112,6 @@ fun MangaScreen(
state: MangaScreenModel.State.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
fetchInterval: Int?, fetchInterval: Int?,
dateRelativeTime: Boolean,
dateFormat: DateFormat,
isTabletUi: Boolean, isTabletUi: Boolean,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
@ -179,8 +176,6 @@ fun MangaScreen(
MangaScreenSmallImpl( MangaScreenSmallImpl(
state = state, state = state,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
fetchInterval = fetchInterval, fetchInterval = fetchInterval,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
@ -226,10 +221,8 @@ fun MangaScreen(
MangaScreenLargeImpl( MangaScreenLargeImpl(
state = state, state = state,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
dateFormat = dateFormat,
fetchInterval = fetchInterval, fetchInterval = fetchInterval,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
@ -276,8 +269,6 @@ fun MangaScreen(
private fun MangaScreenSmallImpl( private fun MangaScreenSmallImpl(
state: MangaScreenModel.State.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Boolean,
dateFormat: DateFormat,
fetchInterval: Int?, fetchInterval: Int?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
@ -585,8 +576,6 @@ private fun MangaScreenSmallImpl(
manga = state.manga, manga = state.manga,
chapters = listItem, chapters = listItem,
isAnyChapterSelected = chapters.fastAny { it.selected }, isAnyChapterSelected = chapters.fastAny { it.selected },
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
// SY --> // SY -->
@ -607,8 +596,6 @@ private fun MangaScreenSmallImpl(
fun MangaScreenLargeImpl( fun MangaScreenLargeImpl(
state: MangaScreenModel.State.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Boolean,
dateFormat: DateFormat,
fetchInterval: Int?, fetchInterval: Int?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
@ -892,8 +879,6 @@ fun MangaScreenLargeImpl(
manga = state.manga, manga = state.manga,
chapters = listItem, chapters = listItem,
isAnyChapterSelected = chapters.fastAny { it.selected }, isAnyChapterSelected = chapters.fastAny { it.selected },
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
// SY --> // SY -->
@ -958,8 +943,6 @@ private fun LazyListScope.sharedChapterItems(
manga: Manga, manga: Manga,
chapters: List<ChapterList>, chapters: List<ChapterList>,
isAnyChapterSelected: Boolean, isAnyChapterSelected: Boolean,
dateRelativeTime: Boolean,
dateFormat: DateFormat,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
// SY --> // SY -->
@ -981,7 +964,6 @@ private fun LazyListScope.sharedChapterItems(
contentType = { MangaScreenItem.CHAPTER }, contentType = { MangaScreenItem.CHAPTER },
) { item -> ) { item ->
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val context = LocalContext.current
when (item) { when (item) {
is ChapterList.MissingCount -> { is ChapterList.MissingCount -> {
@ -1004,11 +986,7 @@ private fun LazyListScope.sharedChapterItems(
if (manga.isEhBasedManga()) { if (manga.isEhBasedManga()) {
MetadataUtil.EX_DATE_FORMAT.format(Date(it)) MetadataUtil.EX_DATE_FORMAT.format(Date(it))
} else { } else {
Date(it).toRelativeString( relativeDateText(Date(item.chapter.dateUpload))
context,
dateRelativeTime,
dateFormat,
)
} }
// SY <-- // SY <--
}, },

View File

@ -17,7 +17,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastAny
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
@ -37,6 +36,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.screens.LoadingScreen
import java.util.Date
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@Composable @Composable
@ -44,7 +44,6 @@ fun UpdateScreen(
state: UpdatesScreenModel.State, state: UpdatesScreenModel.State,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
lastUpdated: Long, lastUpdated: Long,
relativeTime: Boolean,
// SY --> // SY -->
preserveReadingPosition: Boolean, preserveReadingPosition: Boolean,
// SY <-- // SY <--
@ -61,8 +60,6 @@ fun UpdateScreen(
) { ) {
BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) }) BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
val context = LocalContext.current
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
UpdatesAppBar( UpdatesAppBar(
@ -116,7 +113,7 @@ fun UpdateScreen(
updatesLastUpdatedItem(lastUpdated) updatesLastUpdatedItem(lastUpdated)
updatesUiItems( updatesUiItems(
uiModels = state.getUiModel(context, relativeTime), uiModels = state.getUiModel(),
selectionMode = state.selectionMode, selectionMode = state.selectionMode,
// SY --> // SY -->
preserveReadingPosition = preserveReadingPosition, preserveReadingPosition = preserveReadingPosition,
@ -215,6 +212,6 @@ private fun UpdatesBottomBar(
} }
sealed interface UpdatesUiModel { sealed interface UpdatesUiModel {
data class Header(val date: String) : UpdatesUiModel data class Header(val date: Date) : UpdatesUiModel
data class Item(val item: UpdatesItem) : UpdatesUiModel data class Item(val item: UpdatesItem) : UpdatesUiModel
} }

View File

@ -32,6 +32,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.relativeDateText
import eu.kanade.presentation.manga.components.ChapterDownloadAction import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.presentation.manga.components.ChapterDownloadIndicator import eu.kanade.presentation.manga.components.ChapterDownloadIndicator
import eu.kanade.presentation.manga.components.DotSeparatorText import eu.kanade.presentation.manga.components.DotSeparatorText
@ -94,7 +95,7 @@ internal fun LazyListScope.updatesUiItems(
is UpdatesUiModel.Header -> { is UpdatesUiModel.Header -> {
ListGroupHeader( ListGroupHeader(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
text = item.date, text = relativeDateText(item.date),
) )
} }
is UpdatesUiModel.Item -> { is UpdatesUiModel.Item -> {

View File

@ -204,8 +204,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
* *
* @param categoryId the ID of the category to update, or -1 if no category specified. * @param categoryId the ID of the category to update, or -1 if no category specified.
*/ */
private fun addMangaToQueue(categoryId: Long, group: Int, groupExtra: String?) { private suspend fun addMangaToQueue(categoryId: Long, group: Int, groupExtra: String?) {
val libraryManga = runBlocking { getLibraryManga.await() } val libraryManga = getLibraryManga.await()
// SY --> // SY -->
val groupLibraryUpdateType = libraryPreferences.groupLibraryUpdateType().get() val groupLibraryUpdateType = libraryPreferences.groupLibraryUpdateType().get()
// SY <-- // SY <--
@ -237,7 +237,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
when (group) { when (group) {
LibraryGroup.BY_TRACK_STATUS -> { LibraryGroup.BY_TRACK_STATUS -> {
val trackingExtra = groupExtra?.toIntOrNull() ?: -1 val trackingExtra = groupExtra?.toIntOrNull() ?: -1
val tracks = runBlocking { getTracks.await() }.groupBy { it.mangaId } val tracks = getTracks.await().groupBy { it.mangaId }
libraryManga.filter { (manga) -> libraryManga.filter { (manga) ->
val status = tracks[manga.id]?.firstNotNullOfOrNull { track -> val status = tracks[manga.id]?.firstNotNullOfOrNull { track ->
@ -269,7 +269,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get() val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get()
val skippedUpdates = mutableListOf<Pair<Manga, String?>>() val skippedUpdates = mutableListOf<Pair<Manga, String?>>()
val fetchWindow = fetchInterval.getWindow(ZonedDateTime.now()) val (_, fetchWindowUpperBound) = fetchInterval.getWindow(ZonedDateTime.now())
mangaToUpdate = listToUpdate mangaToUpdate = listToUpdate
// SY --> // SY -->
@ -300,7 +300,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
false false
} }
MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && it.manga.nextUpdate > fetchWindow.second -> { MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && it.manga.nextUpdate > fetchWindowUpperBound -> {
skippedUpdates.add( skippedUpdates.add(
it.manga to it.manga to
context.stringResource(MR.strings.skipped_reason_not_in_release_period), context.stringResource(MR.strings.skipped_reason_not_in_release_period),
@ -312,14 +312,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
} }
.sortedBy { it.manga.title } .sortedBy { it.manga.title }
// Warn when excessively checking a single source notifier.showQueueSizeWarningNotificationIfNeeded(mangaToUpdate)
val maxUpdatesFromSource = mangaToUpdate
.groupBy { it.manga.source }
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
.maxOfOrNull { it.value.size } ?: 0
if (maxUpdatesFromSource > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
notifier.showQueueSizeWarningNotification()
}
if (skippedUpdates.isNotEmpty()) { if (skippedUpdates.isNotEmpty()) {
// TODO: surface skipped reasons to user? // TODO: surface skipped reasons to user?

View File

@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.data.download.Downloader
import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationHandler
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.cancelNotification
@ -30,15 +31,22 @@ import tachiyomi.core.i18n.pluralStringResource
import tachiyomi.core.i18n.stringResource import tachiyomi.core.i18n.stringResource
import tachiyomi.core.util.lang.launchUI import tachiyomi.core.util.lang.launchUI
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.math.RoundingMode import java.math.RoundingMode
import java.text.NumberFormat import java.text.NumberFormat
class LibraryUpdateNotifier(private val context: Context) { class LibraryUpdateNotifier(
private val context: Context,
private val securityPreferences: SecurityPreferences = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
) {
private val preferences: SecurityPreferences by injectLazy()
private val percentFormatter = NumberFormat.getPercentInstance().apply { private val percentFormatter = NumberFormat.getPercentInstance().apply {
roundingMode = RoundingMode.DOWN roundingMode = RoundingMode.DOWN
maximumFractionDigits = 0 maximumFractionDigits = 0
@ -88,7 +96,7 @@ class LibraryUpdateNotifier(private val context: Context) {
), ),
) )
if (!preferences.hideNotificationContent().get()) { if (!securityPreferences.hideNotificationContent().get()) {
val updatingText = manga.joinToString("\n") { it.title.chop(40) } val updatingText = manga.joinToString("\n") { it.title.chop(40) }
progressNotificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(updatingText)) progressNotificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(updatingText))
} }
@ -101,7 +109,19 @@ class LibraryUpdateNotifier(private val context: Context) {
) )
} }
fun showQueueSizeWarningNotification() { /**
* Warn when excessively checking any single source.
*/
fun showQueueSizeWarningNotificationIfNeeded(mangaToUpdate: List<LibraryManga>) {
val maxUpdatesFromSource = mangaToUpdate
.groupBy { it.manga.source }
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
.maxOfOrNull { it.value.size } ?: 0
if (maxUpdatesFromSource <= MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
return
}
context.notify( context.notify(
Notifications.ID_LIBRARY_SIZE_WARNING, Notifications.ID_LIBRARY_SIZE_WARNING,
Notifications.CHANNEL_LIBRARY_PROGRESS, Notifications.CHANNEL_LIBRARY_PROGRESS,
@ -151,7 +171,7 @@ class LibraryUpdateNotifier(private val context: Context) {
Notifications.CHANNEL_NEW_CHAPTERS, Notifications.CHANNEL_NEW_CHAPTERS,
) { ) {
setContentTitle(context.stringResource(MR.strings.notification_new_chapters)) setContentTitle(context.stringResource(MR.strings.notification_new_chapters))
if (updates.size == 1 && !preferences.hideNotificationContent().get()) { if (updates.size == 1 && !securityPreferences.hideNotificationContent().get()) {
setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN)) setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN))
} else { } else {
setContentText( setContentText(
@ -162,7 +182,7 @@ class LibraryUpdateNotifier(private val context: Context) {
), ),
) )
if (!preferences.hideNotificationContent().get()) { if (!securityPreferences.hideNotificationContent().get()) {
setStyle( setStyle(
NotificationCompat.BigTextStyle().bigText( NotificationCompat.BigTextStyle().bigText(
updates.joinToString("\n") { updates.joinToString("\n") {
@ -186,7 +206,7 @@ class LibraryUpdateNotifier(private val context: Context) {
} }
// Per-manga notification // Per-manga notification
if (!preferences.hideNotificationContent().get()) { if (!securityPreferences.hideNotificationContent().get()) {
launchUI { launchUI {
context.notify( context.notify(
updates.map { (manga, chapters) -> updates.map { (manga, chapters) ->
@ -363,3 +383,4 @@ class LibraryUpdateNotifier(private val context: Context) {
private const val NOTIF_MAX_CHAPTERS = 5 private const val NOTIF_MAX_CHAPTERS = 5
private const val NOTIF_TITLE_MAX_LEN = 45 private const val NOTIF_TITLE_MAX_LEN = 45
private const val NOTIF_ICON_SIZE = 192 private const val NOTIF_ICON_SIZE = 192
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60

View File

@ -15,7 +15,6 @@ import eu.kanade.domain.manga.model.copyFrom
import eu.kanade.domain.manga.model.toSManga import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.system.isRunning import eu.kanade.tachiyomi.util.system.isRunning
import eu.kanade.tachiyomi.util.system.setForegroundSafely import eu.kanade.tachiyomi.util.system.setForegroundSafely
@ -25,7 +24,6 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import logcat.LogPriority import logcat.LogPriority
@ -92,17 +90,9 @@ class MetadataUpdateJob(private val context: Context, workerParams: WorkerParame
/** /**
* Adds list of manga to be updated. * Adds list of manga to be updated.
*/ */
private fun addMangaToQueue() { private suspend fun addMangaToQueue() {
mangaToUpdate = runBlocking { getLibraryManga.await() } mangaToUpdate = getLibraryManga.await()
notifier.showQueueSizeWarningNotificationIfNeeded(mangaToUpdate)
// Warn when excessively checking a single source
val maxUpdatesFromSource = mangaToUpdate
.groupBy { it.manga.source }
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
.maxOfOrNull { it.value.size } ?: 0
if (maxUpdatesFromSource > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
notifier.showQueueSizeWarningNotification()
}
} }
private suspend fun updateMetadata() { private suspend fun updateMetadata() {

View File

@ -143,8 +143,6 @@ class MangaScreen(
MangaScreen( MangaScreen(
state = successState, state = successState,
snackbarHostState = screenModel.snackbarHostState, snackbarHostState = screenModel.snackbarHostState,
dateRelativeTime = screenModel.relativeTime,
dateFormat = screenModel.dateFormat,
fetchInterval = successState.manga.fetchInterval, fetchInterval = successState.manga.fetchInterval,
isTabletUi = isTabletUi(), isTabletUi = isTabletUi(),
chapterSwipeStartAction = screenModel.chapterSwipeStartAction, chapterSwipeStartAction = screenModel.chapterSwipeStartAction,

View File

@ -5,7 +5,6 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastAny
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
@ -208,8 +207,6 @@ class MangaScreenModel(
val chapterSwipeStartAction = libraryPreferences.swipeToEndAction().get() val chapterSwipeStartAction = libraryPreferences.swipeToEndAction().get()
val chapterSwipeEndAction = libraryPreferences.swipeToStartAction().get() val chapterSwipeEndAction = libraryPreferences.swipeToStartAction().get()
val relativeTime by uiPreferences.relativeTime().asState(screenModelScope)
val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
private val skipFiltered by readerPreferences.skipFiltered().asState(screenModelScope) private val skipFiltered by readerPreferences.skipFiltered().asState(screenModelScope)
val isUpdateIntervalEnabled = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateMangaRestrictions().get() val isUpdateIntervalEnabled = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateMangaRestrictions().get()

View File

@ -1,18 +1,15 @@
package eu.kanade.tachiyomi.ui.updates package eu.kanade.tachiyomi.ui.updates
import android.app.Application import android.app.Application
import android.content.Context
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
import eu.kanade.core.util.addOrRemove import eu.kanade.core.util.addOrRemove
import eu.kanade.core.util.insertSeparators import eu.kanade.core.util.insertSeparators
import eu.kanade.domain.chapter.interactor.SetReadStatus import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.manga.components.ChapterDownloadAction import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.presentation.updates.UpdatesUiModel import eu.kanade.presentation.updates.UpdatesUiModel
import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadCache
@ -21,7 +18,6 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.lang.toDateKey import eu.kanade.tachiyomi.util.lang.toDateKey
import eu.kanade.tachiyomi.util.lang.toRelativeString
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID import exh.source.EXH_SOURCE_ID
import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentList
@ -66,7 +62,6 @@ class UpdatesScreenModel(
private val getChapter: GetChapter = Injekt.get(), private val getChapter: GetChapter = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(),
val snackbarHostState: SnackbarHostState = SnackbarHostState(), val snackbarHostState: SnackbarHostState = SnackbarHostState(),
uiPreferences: UiPreferences = Injekt.get(),
// SY --> // SY -->
readerPreferences: ReaderPreferences = Injekt.get(), readerPreferences: ReaderPreferences = Injekt.get(),
// SY <-- // SY <--
@ -76,7 +71,6 @@ class UpdatesScreenModel(
val events: Flow<Event> = _events.receiveAsFlow() val events: Flow<Event> = _events.receiveAsFlow()
val lastUpdated by libraryPreferences.lastUpdatedTimestamp().asState(screenModelScope) val lastUpdated by libraryPreferences.lastUpdatedTimestamp().asState(screenModelScope)
val relativeTime by uiPreferences.relativeTime().asState(screenModelScope)
// SY --> // SY -->
val preserveReadingPosition by readerPreferences.preserveReadingPosition().asState(screenModelScope) val preserveReadingPosition by readerPreferences.preserveReadingPosition().asState(screenModelScope)
@ -388,9 +382,7 @@ class UpdatesScreenModel(
val selected = items.filter { it.selected } val selected = items.filter { it.selected }
val selectionMode = selected.isNotEmpty() val selectionMode = selected.isNotEmpty()
fun getUiModel(context: Context, relativeTime: Boolean): List<UpdatesUiModel> { fun getUiModel(): List<UpdatesUiModel> {
val dateFormat by mutableStateOf(UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get()))
return items return items
.map { UpdatesUiModel.Item(it) } .map { UpdatesUiModel.Item(it) }
.insertSeparators { before, after -> .insertSeparators { before, after ->
@ -398,12 +390,7 @@ class UpdatesScreenModel(
val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0) val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0)
when { when {
beforeDate.time != afterDate.time && afterDate.time != 0L -> { beforeDate.time != afterDate.time && afterDate.time != 0L -> {
val text = afterDate.toRelativeString( UpdatesUiModel.Header(afterDate)
context = context,
relative = relativeTime,
dateFormat = dateFormat,
)
UpdatesUiModel.Header(text)
} }
// Return null to avoid adding a separator between two items. // Return null to avoid adding a separator between two items.
else -> null else -> null

View File

@ -75,7 +75,6 @@ object UpdatesTab : Tab {
state = state, state = state,
snackbarHostState = screenModel.snackbarHostState, snackbarHostState = screenModel.snackbarHostState,
lastUpdated = screenModel.lastUpdated, lastUpdated = screenModel.lastUpdated,
relativeTime = screenModel.relativeTime,
// SY --> // SY -->
preserveReadingPosition = screenModel.preserveReadingPosition, preserveReadingPosition = screenModel.preserveReadingPosition,
// SY <-- // SY <--