mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-21 20:27:06 +03:00
parent
67d3043533
commit
c85576e06b
24 changed files with 604 additions and 103 deletions
|
@ -3,12 +3,16 @@ package eu.kanade.domain.entries.anime.interactor
|
|||
import eu.kanade.domain.entries.anime.model.hasCustomCover
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
|
||||
import tachiyomi.domain.entries.anime.interactor.getCurrentFetchRange
|
||||
import tachiyomi.domain.entries.anime.interactor.updateIntervalMeta
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.entries.anime.model.AnimeUpdate
|
||||
import tachiyomi.domain.entries.anime.repository.AnimeRepository
|
||||
import tachiyomi.domain.items.episode.model.Episode
|
||||
import tachiyomi.source.local.entries.anime.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
|
||||
class UpdateAnime(
|
||||
|
@ -73,6 +77,21 @@ class UpdateAnime(
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateIntervalMeta(
|
||||
anime: Anime,
|
||||
episodes: List<Episode>,
|
||||
zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
setCurrentFetchRange: Pair<Long, Long> = getCurrentFetchRange(zonedDateTime),
|
||||
): Boolean {
|
||||
val newMeta = updateIntervalMeta(anime, episodes, zonedDateTime, setCurrentFetchRange)
|
||||
|
||||
return if (newMeta != null) {
|
||||
animeRepository.updateAnime(newMeta)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateLastUpdate(animeId: Long): Boolean {
|
||||
return animeRepository.updateAnime(AnimeUpdate(id = animeId, lastUpdate = Date().time))
|
||||
}
|
||||
|
|
|
@ -3,12 +3,16 @@ package eu.kanade.domain.entries.manga.interactor
|
|||
import eu.kanade.domain.entries.manga.model.hasCustomCover
|
||||
import eu.kanade.tachiyomi.data.cache.MangaCoverCache
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import tachiyomi.domain.entries.manga.interactor.getCurrentFetchRange
|
||||
import tachiyomi.domain.entries.manga.interactor.updateIntervalMeta
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.entries.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.entries.manga.repository.MangaRepository
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
import tachiyomi.source.local.entries.manga.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
|
||||
class UpdateManga(
|
||||
|
@ -73,6 +77,21 @@ class UpdateManga(
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateIntervalMeta(
|
||||
manga: Manga,
|
||||
chapters: List<Chapter>,
|
||||
zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
setCurrentFetchRange: Pair<Long, Long> = getCurrentFetchRange(zonedDateTime),
|
||||
): Boolean {
|
||||
val newMeta = updateIntervalMeta(manga, chapters, zonedDateTime, setCurrentFetchRange)
|
||||
|
||||
return if (newMeta != null) {
|
||||
mangaRepository.updateManga(newMeta)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
||||
return mangaRepository.updateManga(MangaUpdate(id = mangaId, lastUpdate = Date().time))
|
||||
}
|
||||
|
|
|
@ -1,18 +1,33 @@
|
|||
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
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
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
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastMap
|
||||
import androidx.core.content.ContextCompat
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
|
@ -41,6 +56,8 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY
|
|||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_HAS_UNVIEWED
|
||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_COMPLETED
|
||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_VIEWED
|
||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_OUTSIDE_RELEASE_PERIOD
|
||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
|
@ -174,11 +191,12 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
val animelibUpdateCategoriesPref = libraryPreferences.animeLibraryUpdateCategories()
|
||||
val animelibUpdateCategoriesExcludePref =
|
||||
libraryPreferences.animeLibraryUpdateCategoriesExclude()
|
||||
val libraryUpdateAnimeRestriction by libraryUpdateMangaRestrictionPref.collectAsState()
|
||||
|
||||
val includedAnime by animelibUpdateCategoriesPref.collectAsState()
|
||||
val excludedAnime by animelibUpdateCategoriesExcludePref.collectAsState()
|
||||
var showAnimeDialog by rememberSaveable { mutableStateOf(false) }
|
||||
if (showAnimeDialog) {
|
||||
var showAnimeCategoriesDialog by rememberSaveable { mutableStateOf(false) }
|
||||
if (showAnimeCategoriesDialog) {
|
||||
TriStateListDialog(
|
||||
title = stringResource(R.string.anime_categories),
|
||||
message = stringResource(R.string.pref_anime_library_update_categories_details),
|
||||
|
@ -186,14 +204,30 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
initialChecked = includedAnime.mapNotNull { id -> allAnimeCategories.find { it.id.toString() == id } },
|
||||
initialInversed = excludedAnime.mapNotNull { id -> allAnimeCategories.find { it.id.toString() == id } },
|
||||
itemLabel = { it.visualName },
|
||||
onDismissRequest = { showAnimeDialog = false },
|
||||
onDismissRequest = { showAnimeCategoriesDialog = false },
|
||||
onValueChanged = { newIncluded, newExcluded ->
|
||||
animelibUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
|
||||
animelibUpdateCategoriesExcludePref.set(
|
||||
newExcluded.map { it.id.toString() }
|
||||
.toSet(),
|
||||
)
|
||||
showAnimeDialog = false
|
||||
showAnimeCategoriesDialog = false
|
||||
},
|
||||
)
|
||||
}
|
||||
val leadAnimeRange by libraryPreferences.leadingAnimeExpectedDays().collectAsState()
|
||||
val followAnimeRange by libraryPreferences.followingAnimeExpectedDays().collectAsState()
|
||||
|
||||
var showFetchAnimeRangesDialog by rememberSaveable { mutableStateOf(false) }
|
||||
if (showFetchAnimeRangesDialog) {
|
||||
LibraryExpectedRangeDialog(
|
||||
initialLead = leadAnimeRange,
|
||||
initialFollow = followAnimeRange,
|
||||
onDismissRequest = { showFetchAnimeRangesDialog = false },
|
||||
onValueChanged = { leadValue, followValue ->
|
||||
libraryPreferences.leadingAnimeExpectedDays().set(leadValue)
|
||||
libraryPreferences.followingAnimeExpectedDays().set(followValue)
|
||||
showFetchAnimeRangesDialog = false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -201,11 +235,12 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
val libraryUpdateCategoriesPref = libraryPreferences.mangaLibraryUpdateCategories()
|
||||
val libraryUpdateCategoriesExcludePref =
|
||||
libraryPreferences.mangaLibraryUpdateCategoriesExclude()
|
||||
val libraryUpdateMangaRestriction by libraryUpdateMangaRestrictionPref.collectAsState()
|
||||
|
||||
val includedManga by libraryUpdateCategoriesPref.collectAsState()
|
||||
val excludedManga by libraryUpdateCategoriesExcludePref.collectAsState()
|
||||
var showMangaDialog by rememberSaveable { mutableStateOf(false) }
|
||||
if (showMangaDialog) {
|
||||
var showMangaCategoriesDialog by rememberSaveable { mutableStateOf(false) }
|
||||
if (showMangaCategoriesDialog) {
|
||||
TriStateListDialog(
|
||||
title = stringResource(R.string.manga_categories),
|
||||
message = stringResource(R.string.pref_manga_library_update_categories_details),
|
||||
|
@ -213,20 +248,36 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
initialChecked = includedManga.mapNotNull { id -> allMangaCategories.find { it.id.toString() == id } },
|
||||
initialInversed = excludedManga.mapNotNull { id -> allMangaCategories.find { it.id.toString() == id } },
|
||||
itemLabel = { it.visualName },
|
||||
onDismissRequest = { showMangaDialog = false },
|
||||
onDismissRequest = { showMangaCategoriesDialog = false },
|
||||
onValueChanged = { newIncluded, newExcluded ->
|
||||
libraryUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
|
||||
libraryUpdateCategoriesExcludePref.set(
|
||||
newExcluded.map { it.id.toString() }
|
||||
.toSet(),
|
||||
)
|
||||
showMangaDialog = false
|
||||
showMangaCategoriesDialog = false
|
||||
},
|
||||
)
|
||||
}
|
||||
val leadMangaRange by libraryPreferences.leadingMangaExpectedDays().collectAsState()
|
||||
val followMangaRange by libraryPreferences.followingMangaExpectedDays().collectAsState()
|
||||
|
||||
var showFetchMangaRangesDialog by rememberSaveable { mutableStateOf(false) }
|
||||
if (showFetchMangaRangesDialog) {
|
||||
LibraryExpectedRangeDialog(
|
||||
initialLead = leadMangaRange,
|
||||
initialFollow = followMangaRange,
|
||||
onDismissRequest = { showFetchMangaRangesDialog = false },
|
||||
onValueChanged = { leadValue, followValue ->
|
||||
libraryPreferences.leadingMangaExpectedDays().set(leadValue)
|
||||
libraryPreferences.followingMangaExpectedDays().set(followValue)
|
||||
showFetchMangaRangesDialog = false
|
||||
},
|
||||
)
|
||||
}
|
||||
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),
|
||||
|
@ -264,15 +315,6 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
true
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.MultiSelectListPreference(
|
||||
pref = libraryUpdateMangaRestrictionPref,
|
||||
title = stringResource(R.string.pref_library_update_manga_restriction),
|
||||
entries = mapOf(
|
||||
ENTRY_HAS_UNVIEWED to stringResource(R.string.pref_update_only_completely_read),
|
||||
ENTRY_NON_VIEWED to stringResource(R.string.pref_update_only_started),
|
||||
ENTRY_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
|
||||
),
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(R.string.anime_categories),
|
||||
subtitle = getCategoriesLabel(
|
||||
|
@ -280,7 +322,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
included = includedAnime,
|
||||
excluded = excludedAnime,
|
||||
),
|
||||
onClick = { showAnimeDialog = true },
|
||||
onClick = { showAnimeCategoriesDialog = true },
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(R.string.manga_categories),
|
||||
|
@ -289,7 +331,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
included = includedManga,
|
||||
excluded = excludedManga,
|
||||
),
|
||||
onClick = { showMangaDialog = true },
|
||||
onClick = { showMangaCategoriesDialog = true },
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = libraryPreferences.autoUpdateMetadata(),
|
||||
|
@ -302,6 +344,39 @@ 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(
|
||||
ENTRY_HAS_UNVIEWED to stringResource(R.string.pref_update_only_completely_read),
|
||||
ENTRY_NON_VIEWED to stringResource(R.string.pref_update_only_started),
|
||||
ENTRY_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
|
||||
ENTRY_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, leadMangaRange, leadMangaRange),
|
||||
pluralStringResource(R.plurals.pref_update_release_following_days, followMangaRange, followMangaRange),
|
||||
).joinToString(),
|
||||
onClick = { showFetchMangaRangesDialog = true },
|
||||
).takeIf { ENTRY_OUTSIDE_RELEASE_PERIOD in libraryUpdateMangaRestriction },
|
||||
Preference.PreferenceItem.InfoPreference(
|
||||
title = stringResource(R.string.pref_update_release_grace_period_info),
|
||||
).takeIf { ENTRY_OUTSIDE_RELEASE_PERIOD in libraryUpdateMangaRestriction },
|
||||
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(R.string.pref_update_anime_release_grace_period),
|
||||
subtitle = listOf(
|
||||
pluralStringResource(R.plurals.pref_update_release_leading_days, leadAnimeRange, leadAnimeRange),
|
||||
pluralStringResource(R.plurals.pref_update_release_following_days, followAnimeRange, followAnimeRange),
|
||||
).joinToString(),
|
||||
onClick = { showFetchAnimeRangesDialog = true },
|
||||
).takeIf { ENTRY_OUTSIDE_RELEASE_PERIOD in libraryUpdateAnimeRestriction },
|
||||
Preference.PreferenceItem.InfoPreference(
|
||||
title = stringResource(R.string.pref_update_release_grace_period_info),
|
||||
).takeIf { ENTRY_OUTSIDE_RELEASE_PERIOD in libraryUpdateAnimeRestriction },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -367,4 +442,85 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LibraryExpectedRangeDialog(
|
||||
initialLead: Int,
|
||||
initialFollow: Int,
|
||||
onDismissRequest: () -> Unit,
|
||||
onValueChanged: (portrait: Int, landscape: Int) -> Unit,
|
||||
) {
|
||||
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 = {
|
||||
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,
|
||||
) {
|
||||
val size = DpSize(width = maxWidth / 2, height = 128.dp)
|
||||
val items = (0..28).map {
|
||||
if (it == 0) {
|
||||
stringResource(R.string.label_default)
|
||||
} else {
|
||||
it.toString()
|
||||
}
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
WheelTextPicker(
|
||||
size = size,
|
||||
items = items,
|
||||
startIndex = leadValue,
|
||||
onSelectionChanged = {
|
||||
leadValue = it
|
||||
},
|
||||
)
|
||||
WheelTextPicker(
|
||||
size = size,
|
||||
items = items,
|
||||
startIndex = followValue,
|
||||
onSelectionChanged = {
|
||||
followValue = it
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { onValueChanged(leadValue, followValue) }) {
|
||||
Text(text = stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,11 +139,11 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
|||
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
|
||||
|
||||
// Updates widget update
|
||||
with(TachiyomiMangaWidgetManager(Injekt.get())) {
|
||||
with(TachiyomiMangaWidgetManager(Injekt.get(), Injekt.get())) {
|
||||
init(ProcessLifecycleOwner.get().lifecycleScope)
|
||||
}
|
||||
|
||||
with(TachiyomiAnimeWidgetManager(Injekt.get())) {
|
||||
with(TachiyomiAnimeWidgetManager(Injekt.get(), Injekt.get())) {
|
||||
init(ProcessLifecycleOwner.get().lifecycleScope)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ class AnimeUpdatesRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun subscribeAllAnimeUpdates(after: Long): Flow<List<AnimeUpdatesWithRelations>> {
|
||||
override fun subscribeAllAnimeUpdates(after: Long, limit: Long): Flow<List<AnimeUpdatesWithRelations>> {
|
||||
return databaseHandler.subscribeToList {
|
||||
animeupdatesViewQueries.animeupdates(after, animeUpdateWithRelationMapper)
|
||||
animeupdatesViewQueries.getRecentAnimeUpdates(after, limit, animeUpdateWithRelationMapper)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ class MangaUpdatesRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun subscribeAllMangaUpdates(after: Long): Flow<List<MangaUpdatesWithRelations>> {
|
||||
override fun subscribeAllMangaUpdates(after: Long, limit: Long): Flow<List<MangaUpdatesWithRelations>> {
|
||||
return databaseHandler.subscribeToList {
|
||||
updatesViewQueries.updates(after, mangaUpdateWithRelationMapper)
|
||||
updatesViewQueries.getRecentUpdates(after, limit, mangaUpdateWithRelationMapper)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,10 +20,11 @@ WHERE favorite = 1
|
|||
AND date_fetch > date_added
|
||||
ORDER BY date_fetch DESC;
|
||||
|
||||
updates:
|
||||
getRecentUpdates:
|
||||
SELECT *
|
||||
FROM updatesView
|
||||
WHERE dateUpload > :after;
|
||||
WHERE dateUpload > :after
|
||||
LIMIT :limit;
|
||||
|
||||
getUpdatesByReadStatus:
|
||||
SELECT *
|
||||
|
|
|
@ -21,10 +21,11 @@ WHERE favorite = 1
|
|||
AND date_fetch > date_added
|
||||
ORDER BY date_fetch DESC;
|
||||
|
||||
animeupdates:
|
||||
getRecentAnimeUpdates:
|
||||
SELECT *
|
||||
FROM animeupdatesView
|
||||
WHERE dateUpload > :after;
|
||||
WHERE dateUpload > :after
|
||||
LIMIT :limit;
|
||||
|
||||
getUpdatesBySeenStatus:
|
||||
SELECT *
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
package tachiyomi.domain.entries.anime.interactor
|
||||
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.entries.anime.model.AnimeUpdate
|
||||
import tachiyomi.domain.items.episode.model.Episode
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
const val MAX_GRACE_PERIOD = 28
|
||||
|
||||
fun updateIntervalMeta(
|
||||
anime: Anime,
|
||||
episodes: List<Episode>,
|
||||
zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
setCurrentFetchRange: Pair<Long, Long> = getCurrentFetchRange(zonedDateTime),
|
||||
): AnimeUpdate? {
|
||||
val currentFetchRange = if (setCurrentFetchRange.first == 0L && setCurrentFetchRange.second == 0L) {
|
||||
getCurrentFetchRange(ZonedDateTime.now())
|
||||
} else {
|
||||
setCurrentFetchRange
|
||||
}
|
||||
val interval = anime.calculateInterval.takeIf { it < 0 } ?: calculateInterval(episodes, zonedDateTime)
|
||||
val nextUpdate = calculateNextUpdate(anime, interval, zonedDateTime, currentFetchRange)
|
||||
|
||||
return if (anime.nextUpdate == nextUpdate && anime.calculateInterval == interval) {
|
||||
null
|
||||
} else { AnimeUpdate(id = anime.id, nextUpdate = nextUpdate, calculateInterval = interval) }
|
||||
}
|
||||
|
||||
fun calculateInterval(episodes: List<Episode>, zonedDateTime: ZonedDateTime): Int {
|
||||
val sortedEpisodes = episodes
|
||||
.sortedWith(compareByDescending<Episode> { it.dateUpload }.thenByDescending { it.dateFetch })
|
||||
.take(50)
|
||||
|
||||
val uploadDates = sortedEpisodes
|
||||
.filter { it.dateUpload > 0L }
|
||||
.map {
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zonedDateTime.zone)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
}
|
||||
.distinct()
|
||||
val fetchDates = sortedEpisodes
|
||||
.map {
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zonedDateTime.zone)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
}
|
||||
.distinct()
|
||||
|
||||
val newInterval = when {
|
||||
// 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
|
||||
fetchDates.size >= 3 -> {
|
||||
val fetchDelta = fetchDates.last().until(fetchDates.first(), ChronoUnit.DAYS)
|
||||
val uploadPeriod = fetchDates.indexOf(fetchDates.last())
|
||||
(fetchDelta).floorDiv(uploadPeriod).toInt()
|
||||
}
|
||||
// Default to 7 days
|
||||
else -> 7
|
||||
}
|
||||
// min 1, max 28 days
|
||||
return newInterval.coerceIn(1, MAX_GRACE_PERIOD)
|
||||
}
|
||||
|
||||
private fun calculateNextUpdate(
|
||||
anime: Anime,
|
||||
interval: Int,
|
||||
zonedDateTime: ZonedDateTime,
|
||||
currentFetchRange: Pair<Long, Long>,
|
||||
): Long {
|
||||
return if (anime.nextUpdate !in currentFetchRange.first.rangeTo(currentFetchRange.second + 1) ||
|
||||
anime.calculateInterval == 0
|
||||
) {
|
||||
val latestDate = ZonedDateTime.ofInstant(Instant.ofEpochMilli(anime.lastUpdate), zonedDateTime.zone).toLocalDate().atStartOfDay()
|
||||
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, zonedDateTime).toInt()
|
||||
val cycle = timeSinceLatest.floorDiv(interval.absoluteValue.takeIf { interval < 0 } ?: doubleInterval(interval, timeSinceLatest, doubleWhenOver = 10, maxValue = 28))
|
||||
latestDate.plusDays((cycle + 1) * interval.toLong()).toEpochSecond(zonedDateTime.offset) * 1000
|
||||
} else {
|
||||
anime.nextUpdate
|
||||
}
|
||||
}
|
||||
|
||||
private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int, maxValue: Int): Int {
|
||||
if (delta >= maxValue) return maxValue
|
||||
val cycle = timeSinceLatest.floorDiv(delta) + 1
|
||||
// double delta again if missed more than 9 check in new delta
|
||||
return if (cycle > doubleWhenOver) {
|
||||
doubleInterval(delta * 2, timeSinceLatest, doubleWhenOver, maxValue)
|
||||
} else {
|
||||
delta
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrentFetchRange(
|
||||
timeToCal: ZonedDateTime,
|
||||
): Pair<Long, Long> {
|
||||
val preferences: LibraryPreferences = Injekt.get()
|
||||
|
||||
// lead range and the following range depend on if updateOnlyExpectedPeriod set.
|
||||
var followRange = 0
|
||||
var leadRange = 0
|
||||
if (LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in preferences.libraryUpdateItemRestriction().get()) {
|
||||
followRange = preferences.followingAnimeExpectedDays().get()
|
||||
leadRange = preferences.leadingAnimeExpectedDays().get()
|
||||
}
|
||||
val startToday = timeToCal.toLocalDate().atStartOfDay(timeToCal.zone)
|
||||
// revert math of (next_update + follow < now) become (next_update < now - follow)
|
||||
// so (now - follow) become lower limit
|
||||
val lowerRange = startToday.minusDays(followRange.toLong())
|
||||
val higherRange = startToday.plusDays(leadRange.toLong())
|
||||
return Pair(lowerRange.toEpochSecond() * 1000, higherRange.toEpochSecond() * 1000 - 1)
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package tachiyomi.domain.entries.manga.interactor
|
||||
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.entries.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
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<Chapter>,
|
||||
zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
setCurrentFetchRange: Pair<Long, Long> = getCurrentFetchRange(zonedDateTime),
|
||||
): MangaUpdate? {
|
||||
val currentFetchRange = if (setCurrentFetchRange.first == 0L && setCurrentFetchRange.second == 0L) {
|
||||
getCurrentFetchRange(ZonedDateTime.now())
|
||||
} else {
|
||||
setCurrentFetchRange
|
||||
}
|
||||
val interval = manga.calculateInterval.takeIf { it < 0 } ?: calculateInterval(chapters, zonedDateTime)
|
||||
val nextUpdate = calculateNextUpdate(manga, interval, zonedDateTime, currentFetchRange)
|
||||
|
||||
return if (manga.nextUpdate == nextUpdate && manga.calculateInterval == interval) {
|
||||
null
|
||||
} else { MangaUpdate(id = manga.id, nextUpdate = nextUpdate, calculateInterval = interval) }
|
||||
}
|
||||
|
||||
fun calculateInterval(chapters: List<Chapter>, zonedDateTime: ZonedDateTime): Int {
|
||||
val sortedChapters = chapters
|
||||
.sortedWith(compareByDescending<Chapter> { 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
|
||||
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
|
||||
fetchDates.size >= 3 -> {
|
||||
val fetchDelta = fetchDates.last().until(fetchDates.first(), ChronoUnit.DAYS)
|
||||
val uploadPeriod = fetchDates.indexOf(fetchDates.last())
|
||||
(fetchDelta).floorDiv(uploadPeriod).toInt()
|
||||
}
|
||||
// Default to 7 days
|
||||
else -> 7
|
||||
}
|
||||
// min 1, max 28 days
|
||||
return newInterval.coerceIn(1, MAX_GRACE_PERIOD)
|
||||
}
|
||||
|
||||
private fun calculateNextUpdate(
|
||||
manga: Manga,
|
||||
interval: Int,
|
||||
zonedDateTime: ZonedDateTime,
|
||||
currentFetchRange: Pair<Long, Long>,
|
||||
): Long {
|
||||
return if (manga.nextUpdate !in currentFetchRange.first.rangeTo(currentFetchRange.second + 1) ||
|
||||
manga.calculateInterval == 0
|
||||
) {
|
||||
val latestDate = ZonedDateTime.ofInstant(Instant.ofEpochMilli(manga.lastUpdate), zonedDateTime.zone).toLocalDate().atStartOfDay()
|
||||
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, zonedDateTime).toInt()
|
||||
val cycle = timeSinceLatest.floorDiv(interval.absoluteValue.takeIf { interval < 0 } ?: doubleInterval(interval, timeSinceLatest, doubleWhenOver = 10, maxValue = 28))
|
||||
latestDate.plusDays((cycle + 1) * interval.toLong()).toEpochSecond(zonedDateTime.offset) * 1000
|
||||
} else {
|
||||
manga.nextUpdate
|
||||
}
|
||||
}
|
||||
|
||||
private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int, maxValue: Int): Int {
|
||||
if (delta >= maxValue) return maxValue
|
||||
val cycle = timeSinceLatest.floorDiv(delta) + 1
|
||||
// double delta again if missed more than 9 check in new delta
|
||||
return if (cycle > doubleWhenOver) {
|
||||
doubleInterval(delta * 2, timeSinceLatest, doubleWhenOver, maxValue)
|
||||
} else {
|
||||
delta
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrentFetchRange(
|
||||
timeToCal: ZonedDateTime,
|
||||
): Pair<Long, Long> {
|
||||
val preferences: LibraryPreferences = Injekt.get()
|
||||
|
||||
// lead range and the following range depend on if updateOnlyExpectedPeriod set.
|
||||
var followRange = 0
|
||||
var leadRange = 0
|
||||
if (LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in preferences.libraryUpdateItemRestriction().get()) {
|
||||
followRange = preferences.followingMangaExpectedDays().get()
|
||||
leadRange = preferences.leadingMangaExpectedDays().get()
|
||||
}
|
||||
val startToday = timeToCal.toLocalDate().atStartOfDay(timeToCal.zone)
|
||||
// revert math of (next_update + follow < now) become (next_update < now - follow)
|
||||
// so (now - follow) become lower limit
|
||||
val lowerRange = startToday.minusDays(followRange.toLong())
|
||||
val higherRange = startToday.plusDays(leadRange.toLong())
|
||||
return Pair(lowerRange.toEpochSecond() * 1000, higherRange.toEpochSecond() * 1000 - 1)
|
||||
}
|
|
@ -58,9 +58,16 @@ class LibraryPreferences(
|
|||
ENTRY_HAS_UNVIEWED,
|
||||
ENTRY_NON_COMPLETED,
|
||||
ENTRY_NON_VIEWED,
|
||||
ENTRY_OUTSIDE_RELEASE_PERIOD,
|
||||
),
|
||||
)
|
||||
|
||||
fun leadingAnimeExpectedDays() = preferenceStore.getInt("pref_library_before_expect_key", 1)
|
||||
fun followingAnimeExpectedDays() = preferenceStore.getInt("pref_library_after_expect_key", 1)
|
||||
|
||||
fun leadingMangaExpectedDays() = preferenceStore.getInt("pref_library_before_expect_key", 1)
|
||||
fun followingMangaExpectedDays() = preferenceStore.getInt("pref_library_after_expect_key", 1)
|
||||
|
||||
fun autoUpdateMetadata() = preferenceStore.getBoolean("auto_update_metadata", false)
|
||||
|
||||
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
|
||||
|
@ -132,6 +139,26 @@ class LibraryPreferences(
|
|||
fun filterCompletedManga() =
|
||||
preferenceStore.getEnum("pref_filter_library_completed_v2", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterIntervalCustomAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_custom", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterIntervalCustomManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_custom", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterIntervalLongAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_long", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterIntervalLongManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_long", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterIntervalLateAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_late", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterIntervalLateManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_late", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterIntervalDroppedAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_dropped", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterIntervalDroppedManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_dropped", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterIntervalPassedAnime() = preferenceStore.getEnum("pref_filter_anime_library_interval_passed", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterIntervalPassedManga() = preferenceStore.getEnum("pref_filter_manga_library_interval_passed", TriStateFilter.DISABLED)
|
||||
|
||||
fun filterTrackedAnime(id: Int) =
|
||||
preferenceStore.getEnum("pref_filter_animelib_tracked_${id}_v2", TriStateFilter.DISABLED)
|
||||
|
||||
|
@ -275,5 +302,6 @@ class LibraryPreferences(
|
|||
const val ENTRY_NON_COMPLETED = "manga_ongoing"
|
||||
const val ENTRY_HAS_UNVIEWED = "manga_fully_read"
|
||||
const val ENTRY_NON_VIEWED = "manga_started"
|
||||
const val ENTRY_OUTSIDE_RELEASE_PERIOD = "manga_outside_release_period"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,8 @@ class GetAnimeUpdates(
|
|||
return repository.awaitWithSeen(seen, after)
|
||||
}
|
||||
|
||||
fun subscribe(calendar: Calendar): Flow<List<AnimeUpdatesWithRelations>> = subscribe(calendar.time.time)
|
||||
|
||||
fun subscribe(after: Long): Flow<List<AnimeUpdatesWithRelations>> {
|
||||
return repository.subscribeAllAnimeUpdates(after)
|
||||
fun subscribe(calendar: Calendar): Flow<List<AnimeUpdatesWithRelations>> {
|
||||
return repository.subscribeAllAnimeUpdates(calendar.time.time, limit = 250)
|
||||
}
|
||||
|
||||
fun subscribe(seen: Boolean, after: Long): Flow<List<AnimeUpdatesWithRelations>> {
|
||||
|
|
|
@ -7,7 +7,7 @@ interface AnimeUpdatesRepository {
|
|||
|
||||
suspend fun awaitWithSeen(seen: Boolean, after: Long): List<AnimeUpdatesWithRelations>
|
||||
|
||||
fun subscribeAllAnimeUpdates(after: Long): Flow<List<AnimeUpdatesWithRelations>>
|
||||
fun subscribeAllAnimeUpdates(after: Long, limit: Long): Flow<List<AnimeUpdatesWithRelations>>
|
||||
|
||||
fun subscribeWithSeen(seen: Boolean, after: Long): Flow<List<AnimeUpdatesWithRelations>>
|
||||
}
|
||||
|
|
|
@ -13,10 +13,8 @@ class GetMangaUpdates(
|
|||
return repository.awaitWithRead(read, after)
|
||||
}
|
||||
|
||||
fun subscribe(calendar: Calendar): Flow<List<MangaUpdatesWithRelations>> = subscribe(calendar.time.time)
|
||||
|
||||
fun subscribe(after: Long): Flow<List<MangaUpdatesWithRelations>> {
|
||||
return repository.subscribeAllMangaUpdates(after)
|
||||
fun subscribe(calendar: Calendar): Flow<List<MangaUpdatesWithRelations>> {
|
||||
return repository.subscribeAllMangaUpdates(calendar.time.time, limit = 250)
|
||||
}
|
||||
|
||||
fun subscribe(read: Boolean, after: Long): Flow<List<MangaUpdatesWithRelations>> {
|
||||
|
|
|
@ -7,7 +7,7 @@ interface MangaUpdatesRepository {
|
|||
|
||||
suspend fun awaitWithRead(read: Boolean, after: Long): List<MangaUpdatesWithRelations>
|
||||
|
||||
fun subscribeAllMangaUpdates(after: Long): Flow<List<MangaUpdatesWithRelations>>
|
||||
fun subscribeAllMangaUpdates(after: Long, limit: Long): Flow<List<MangaUpdatesWithRelations>>
|
||||
|
||||
fun subscribeWithRead(read: Boolean, after: Long): Flow<List<MangaUpdatesWithRelations>>
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ corektx = "androidx.core:core-ktx:1.11.0-beta01"
|
|||
splashscreen = "androidx.core:core-splashscreen:1.0.0-alpha02"
|
||||
recyclerview = "androidx.recyclerview:recyclerview:1.3.1-rc01"
|
||||
viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01"
|
||||
glance = "androidx.glance:glance-appwidget:1.0.0-alpha03"
|
||||
glance = "androidx.glance:glance-appwidget:1.0.0-beta01"
|
||||
profileinstaller = "androidx.profileinstaller:profileinstaller:1.3.1"
|
||||
mediasession = "androidx.media:media:1.6.0"
|
||||
|
||||
|
|
|
@ -320,6 +320,8 @@
|
|||
|
||||
<string name="pref_category_hide_hidden">Hide hidden categories from categories screen</string>
|
||||
|
||||
<string name="pref_update_anime_release_grace_period">Expected anime release grace period</string>
|
||||
|
||||
<!-- TachiyomiSY -->
|
||||
<string name="data_saver_exclude">Exclude from data saver</string>
|
||||
<string name="data_saver_stop_exclude">Stop excluding from data saver</string>
|
||||
|
|
|
@ -42,10 +42,16 @@
|
|||
<string name="action_settings">Settings</string>
|
||||
<string name="action_menu">Menu</string>
|
||||
<string name="action_filter">Filter</string>
|
||||
<string name="action_set_interval">Set interval</string>
|
||||
<string name="action_filter_bookmarked">Bookmarked</string>
|
||||
<string name="action_filter_tracked">Tracked</string>
|
||||
<string name="action_filter_unread">Unread</string>
|
||||
<string name="action_filter_started">Started</string>
|
||||
<string name="action_filter_interval_custom">Customized fetch interval</string>
|
||||
<string name="action_filter_interval_long">Fetch monthly (28 days)</string>
|
||||
<string name="action_filter_interval_late">Late 10+ check</string>
|
||||
<string name="action_filter_interval_dropped">Dropped? Late 20+ and 2 months</string>
|
||||
<string name="action_filter_interval_passed">Passed check period</string>
|
||||
<!-- reserved for #4048 -->
|
||||
<string name="action_filter_empty">Remove filter</string>
|
||||
<string name="action_sort_alpha">Alphabetically</string>
|
||||
|
@ -53,6 +59,7 @@
|
|||
<string name="action_sort_total">Total chapters</string>
|
||||
<string name="action_sort_last_read">Last read</string>
|
||||
<string name="action_sort_unread_count">Unread count</string>
|
||||
<string name="action_sort_next_updated">Next expected update</string>
|
||||
<string name="action_sort_latest_chapter">Latest chapter</string>
|
||||
<string name="action_sort_chapter_fetch_date">Chapter fetch date</string>
|
||||
<string name="action_sort_date_added">Date added</string>
|
||||
|
@ -255,6 +262,18 @@
|
|||
<string name="pref_library_update_manga_restriction">Skip updating entries</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_update_only_in_release_period">Outside expected release period</string>
|
||||
|
||||
<string name="pref_update_release_grace_period">Expected manga release grace period</string>
|
||||
<plurals name="pref_update_release_leading_days">
|
||||
<item quantity="one">%d day before</item>
|
||||
<item quantity="other">%d days before</item>
|
||||
</plurals>
|
||||
<plurals name="pref_update_release_following_days">
|
||||
<item quantity="one">%d day after</item>
|
||||
<item quantity="other">%d days after</item>
|
||||
</plurals>
|
||||
<string name="pref_update_release_grace_period_info">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.</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_trackers">Automatically refresh trackers</string>
|
||||
|
@ -580,6 +599,7 @@
|
|||
<string name="updating_category">Updating category</string>
|
||||
<string name="local_source_badge">Local</string>
|
||||
<string name="downloaded_chapters">Downloaded chapters</string>
|
||||
<string name="intervals_header">Intervals</string>
|
||||
<!-- For badges/buttons on library covers. -->
|
||||
<string name="overlay_header">Overlay</string>
|
||||
<string name="tabs_header">Tabs</string>
|
||||
|
@ -605,6 +625,10 @@
|
|||
<string name="local_invalid_format">Invalid chapter format</string>
|
||||
<string name="local_filter_order_by">Order by</string>
|
||||
<string name="date">Date</string>
|
||||
<plurals name="day">
|
||||
<item quantity="one">1 day</item>
|
||||
<item quantity="other">%d days</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Item info -->
|
||||
<plurals name="missing_items">
|
||||
|
@ -644,6 +668,10 @@
|
|||
|
||||
<!-- Manga chapters / Anime episodes -->
|
||||
<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_modify_interval_title">Modify interval</string>
|
||||
<string name="manga_modify_calculated_interval_title">Customize Interval</string>
|
||||
<string name="chapter_downloading_progress">Downloading (%1$d/%2$d)</string>
|
||||
<string name="show_title">Source title</string>
|
||||
<string name="show_chapter_number">Chapter number</string>
|
||||
|
@ -837,6 +865,7 @@
|
|||
<string name="skipped_reason_not_caught_up">Skipped because there are unread chapters</string>
|
||||
<string name="skipped_reason_not_started">Skipped because no chapters are read</string>
|
||||
<string name="skipped_reason_not_always_update">Skipped because series does not require updates</string>
|
||||
<string name="skipped_reason_not_in_release_period">Skipped because no release was expected today</string>
|
||||
|
||||
<!-- File Picker Titles -->
|
||||
<string name="file_select_cover">Select cover image</string>
|
||||
|
|
|
@ -4,5 +4,5 @@ import androidx.glance.appwidget.GlanceAppWidget
|
|||
import androidx.glance.appwidget.GlanceAppWidgetReceiver
|
||||
|
||||
class AnimeUpdatesGridGlanceReceiver : GlanceAppWidgetReceiver() {
|
||||
override val glanceAppWidget: GlanceAppWidget = AnimeUpdatesGridGlanceWidget().apply { loadData() }
|
||||
override val glanceAppWidget: GlanceAppWidget = AnimeUpdatesGridGlanceWidget()
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
package tachiyomi.presentation.widget.entries.anime
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.glance.GlanceId
|
||||
import androidx.glance.GlanceModifier
|
||||
import androidx.glance.ImageProvider
|
||||
import androidx.glance.appwidget.GlanceAppWidget
|
||||
import androidx.glance.appwidget.GlanceAppWidgetManager
|
||||
import androidx.glance.appwidget.SizeMode
|
||||
import androidx.glance.appwidget.appWidgetBackground
|
||||
import androidx.glance.appwidget.updateAll
|
||||
import androidx.glance.appwidget.provideContent
|
||||
import androidx.glance.background
|
||||
import androidx.glance.layout.fillMaxSize
|
||||
import coil.executeBlocking
|
||||
|
@ -23,8 +24,7 @@ import coil.size.Scale
|
|||
import coil.transform.RoundedCornersTransformation
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import kotlinx.coroutines.MainScope
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.domain.entries.anime.model.AnimeCover
|
||||
import tachiyomi.domain.updates.anime.interactor.GetAnimeUpdates
|
||||
import tachiyomi.domain.updates.anime.model.AnimeUpdatesWithRelations
|
||||
|
@ -46,33 +46,29 @@ class AnimeUpdatesGridGlanceWidget : GlanceAppWidget() {
|
|||
private val app: Application by injectLazy()
|
||||
private val preferences: SecurityPreferences by injectLazy()
|
||||
|
||||
private val coroutineScope = MainScope()
|
||||
|
||||
private var data: List<Pair<Long, Bitmap?>>? = null
|
||||
|
||||
override val sizeMode = SizeMode.Exact
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||
val locked = preferences.useAuthenticator().get()
|
||||
if (!locked) loadData()
|
||||
|
||||
provideContent {
|
||||
// If app lock enabled, don't do anything
|
||||
if (preferences.useAuthenticator().get()) {
|
||||
if (locked) {
|
||||
LockedAnimeWidget()
|
||||
return
|
||||
return@provideContent
|
||||
}
|
||||
UpdatesAnimeWidget(data)
|
||||
}
|
||||
|
||||
fun loadData(list: List<AnimeUpdatesWithRelations>? = null) {
|
||||
coroutineScope.launchIO {
|
||||
// Don't show anything when lock is active
|
||||
if (preferences.useAuthenticator().get()) {
|
||||
updateAll(app)
|
||||
return@launchIO
|
||||
}
|
||||
|
||||
private suspend fun loadData(list: List<AnimeUpdatesWithRelations>? = null) {
|
||||
withIOContext {
|
||||
val manager = GlanceAppWidgetManager(app)
|
||||
val ids = manager.getGlanceIds(this@AnimeUpdatesGridGlanceWidget::class.java)
|
||||
if (ids.isEmpty()) return@launchIO
|
||||
if (ids.isEmpty()) return@withIOContext
|
||||
|
||||
val processList = list
|
||||
?: Injekt.get<GetAnimeUpdates>().await(
|
||||
|
@ -85,7 +81,6 @@ class AnimeUpdatesGridGlanceWidget : GlanceAppWidget() {
|
|||
.calculateRowAndColumnCount()
|
||||
|
||||
data = prepareList(processList, rowCount * columnCount)
|
||||
ids.forEach { update(app, it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,36 @@
|
|||
package tachiyomi.presentation.widget.entries.anime
|
||||
|
||||
import android.content.Context
|
||||
import androidx.glance.appwidget.GlanceAppWidgetManager
|
||||
import androidx.glance.appwidget.updateAll
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.updates.anime.interactor.GetAnimeUpdates
|
||||
|
||||
class TachiyomiAnimeWidgetManager(
|
||||
private val getUpdates: GetAnimeUpdates,
|
||||
private val securityPreferences: SecurityPreferences,
|
||||
) {
|
||||
|
||||
fun Context.init(scope: LifecycleCoroutineScope) {
|
||||
getUpdates.subscribe(
|
||||
seen = false,
|
||||
after = AnimeUpdatesGridGlanceWidget.DateLimit.timeInMillis,
|
||||
combine(
|
||||
getUpdates.subscribe(seen = false, after = AnimeUpdatesGridGlanceWidget.DateLimit.timeInMillis),
|
||||
securityPreferences.useAuthenticator().changes(),
|
||||
transform = { a, _ -> a },
|
||||
)
|
||||
.drop(1)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
val manager = GlanceAppWidgetManager(this)
|
||||
if (manager.getGlanceIds(AnimeUpdatesGridGlanceWidget::class.java).isNotEmpty()) {
|
||||
AnimeUpdatesGridGlanceWidget().loadData(it)
|
||||
try {
|
||||
AnimeUpdatesGridGlanceWidget().updateAll(this)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e) { "Failed to update widget" }
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
|
|
|
@ -4,5 +4,5 @@ import androidx.glance.appwidget.GlanceAppWidget
|
|||
import androidx.glance.appwidget.GlanceAppWidgetReceiver
|
||||
|
||||
class MangaUpdatesGridGlanceReceiver : GlanceAppWidgetReceiver() {
|
||||
override val glanceAppWidget: GlanceAppWidget = MangaUpdatesGridGlanceWidget().apply { loadData() }
|
||||
override val glanceAppWidget: GlanceAppWidget = MangaUpdatesGridGlanceWidget()
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
package tachiyomi.presentation.widget.entries.manga
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.glance.GlanceId
|
||||
import androidx.glance.GlanceModifier
|
||||
import androidx.glance.ImageProvider
|
||||
import androidx.glance.appwidget.GlanceAppWidget
|
||||
import androidx.glance.appwidget.GlanceAppWidgetManager
|
||||
import androidx.glance.appwidget.SizeMode
|
||||
import androidx.glance.appwidget.appWidgetBackground
|
||||
import androidx.glance.appwidget.updateAll
|
||||
import androidx.glance.appwidget.provideContent
|
||||
import androidx.glance.background
|
||||
import androidx.glance.layout.fillMaxSize
|
||||
import coil.executeBlocking
|
||||
|
@ -23,8 +24,7 @@ import coil.size.Scale
|
|||
import coil.transform.RoundedCornersTransformation
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import kotlinx.coroutines.MainScope
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.domain.entries.manga.model.MangaCover
|
||||
import tachiyomi.domain.updates.manga.interactor.GetMangaUpdates
|
||||
import tachiyomi.domain.updates.manga.model.MangaUpdatesWithRelations
|
||||
|
@ -46,33 +46,29 @@ class MangaUpdatesGridGlanceWidget : GlanceAppWidget() {
|
|||
private val app: Application by injectLazy()
|
||||
private val preferences: SecurityPreferences by injectLazy()
|
||||
|
||||
private val coroutineScope = MainScope()
|
||||
|
||||
private var data: List<Pair<Long, Bitmap?>>? = null
|
||||
|
||||
override val sizeMode = SizeMode.Exact
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||
val locked = preferences.useAuthenticator().get()
|
||||
if (!locked) loadData()
|
||||
|
||||
provideContent {
|
||||
// If app lock enabled, don't do anything
|
||||
if (preferences.useAuthenticator().get()) {
|
||||
if (locked) {
|
||||
LockedMangaWidget()
|
||||
return
|
||||
return@provideContent
|
||||
}
|
||||
UpdatesMangaWidget(data)
|
||||
}
|
||||
|
||||
fun loadData(list: List<MangaUpdatesWithRelations>? = null) {
|
||||
coroutineScope.launchIO {
|
||||
// Don't show anything when lock is active
|
||||
if (preferences.useAuthenticator().get()) {
|
||||
updateAll(app)
|
||||
return@launchIO
|
||||
}
|
||||
|
||||
private suspend fun loadData(list: List<MangaUpdatesWithRelations>? = null) {
|
||||
withIOContext {
|
||||
val manager = GlanceAppWidgetManager(app)
|
||||
val ids = manager.getGlanceIds(this@MangaUpdatesGridGlanceWidget::class.java)
|
||||
if (ids.isEmpty()) return@launchIO
|
||||
if (ids.isEmpty()) return@withIOContext
|
||||
|
||||
val processList = list
|
||||
?: Injekt.get<GetMangaUpdates>().await(
|
||||
|
@ -85,7 +81,6 @@ class MangaUpdatesGridGlanceWidget : GlanceAppWidget() {
|
|||
.calculateRowAndColumnCount()
|
||||
|
||||
data = prepareList(processList, rowCount * columnCount)
|
||||
ids.forEach { update(app, it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,36 @@
|
|||
package tachiyomi.presentation.widget.entries.manga
|
||||
|
||||
import android.content.Context
|
||||
import androidx.glance.appwidget.GlanceAppWidgetManager
|
||||
import androidx.glance.appwidget.updateAll
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.updates.manga.interactor.GetMangaUpdates
|
||||
|
||||
class TachiyomiMangaWidgetManager(
|
||||
private val getUpdates: GetMangaUpdates,
|
||||
private val securityPreferences: SecurityPreferences,
|
||||
) {
|
||||
|
||||
fun Context.init(scope: LifecycleCoroutineScope) {
|
||||
getUpdates.subscribe(
|
||||
read = false,
|
||||
after = MangaUpdatesGridGlanceWidget.DateLimit.timeInMillis,
|
||||
combine(
|
||||
getUpdates.subscribe(read = false, after = MangaUpdatesGridGlanceWidget.DateLimit.timeInMillis),
|
||||
securityPreferences.useAuthenticator().changes(),
|
||||
transform = { a, _ -> a },
|
||||
)
|
||||
.drop(1)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
val manager = GlanceAppWidgetManager(this)
|
||||
if (manager.getGlanceIds(MangaUpdatesGridGlanceWidget::class.java).isNotEmpty()) {
|
||||
MangaUpdatesGridGlanceWidget().loadData(it)
|
||||
try {
|
||||
MangaUpdatesGridGlanceWidget().updateAll(this)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e) { "Failed to update widget" }
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
|
|
Loading…
Reference in a new issue