Last commit merged: 6a558ad119
This commit is contained in:
LuftVerbot 2023-11-18 16:01:50 +01:00
parent 1b6301cc95
commit a2a445fdc5
69 changed files with 445 additions and 325 deletions

View file

@ -177,6 +177,7 @@ dependencies {
implementation(androidx.paging.compose) implementation(androidx.paging.compose)
implementation(libs.bundles.sqlite) implementation(libs.bundles.sqlite)
implementation(libs.sqldelight.primitive.adapters)
implementation(kotlinx.reflect) implementation(kotlinx.reflect)

View file

@ -81,7 +81,7 @@ import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime
import tachiyomi.domain.entries.anime.interactor.NetworkToLocalAnime import tachiyomi.domain.entries.anime.interactor.NetworkToLocalAnime
import tachiyomi.domain.entries.anime.interactor.ResetAnimeViewerFlags import tachiyomi.domain.entries.anime.interactor.ResetAnimeViewerFlags
import tachiyomi.domain.entries.anime.interactor.SetAnimeEpisodeFlags import tachiyomi.domain.entries.anime.interactor.SetAnimeEpisodeFlags
import tachiyomi.domain.entries.anime.interactor.SetAnimeUpdateInterval import tachiyomi.domain.entries.anime.interactor.SetAnimeFetchInterval
import tachiyomi.domain.entries.anime.repository.AnimeRepository import tachiyomi.domain.entries.anime.repository.AnimeRepository
import tachiyomi.domain.entries.manga.interactor.GetDuplicateLibraryManga import tachiyomi.domain.entries.manga.interactor.GetDuplicateLibraryManga
import tachiyomi.domain.entries.manga.interactor.GetLibraryManga import tachiyomi.domain.entries.manga.interactor.GetLibraryManga
@ -91,7 +91,7 @@ import tachiyomi.domain.entries.manga.interactor.GetMangaWithChapters
import tachiyomi.domain.entries.manga.interactor.NetworkToLocalManga import tachiyomi.domain.entries.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.entries.manga.interactor.ResetMangaViewerFlags import tachiyomi.domain.entries.manga.interactor.ResetMangaViewerFlags
import tachiyomi.domain.entries.manga.interactor.SetMangaChapterFlags import tachiyomi.domain.entries.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.entries.manga.interactor.SetMangaUpdateInterval import tachiyomi.domain.entries.manga.interactor.SetMangaFetchInterval
import tachiyomi.domain.entries.manga.repository.MangaRepository import tachiyomi.domain.entries.manga.repository.MangaRepository
import tachiyomi.domain.history.anime.interactor.GetAnimeHistory import tachiyomi.domain.history.anime.interactor.GetAnimeHistory
import tachiyomi.domain.history.anime.interactor.GetNextEpisodes import tachiyomi.domain.history.anime.interactor.GetNextEpisodes
@ -184,7 +184,7 @@ class DomainModule : InjektModule {
addFactory { GetNextEpisodes(get(), get(), get()) } addFactory { GetNextEpisodes(get(), get(), get()) }
addFactory { ResetAnimeViewerFlags(get()) } addFactory { ResetAnimeViewerFlags(get()) }
addFactory { SetAnimeEpisodeFlags(get()) } addFactory { SetAnimeEpisodeFlags(get()) }
addFactory { SetAnimeUpdateInterval(get()) } addFactory { SetAnimeFetchInterval(get()) }
addFactory { SetAnimeDefaultEpisodeFlags(get(), get(), get()) } addFactory { SetAnimeDefaultEpisodeFlags(get(), get(), get()) }
addFactory { SetAnimeViewerFlags(get()) } addFactory { SetAnimeViewerFlags(get()) }
addFactory { NetworkToLocalAnime(get()) } addFactory { NetworkToLocalAnime(get()) }
@ -200,7 +200,7 @@ class DomainModule : InjektModule {
addFactory { GetNextChapters(get(), get(), get()) } addFactory { GetNextChapters(get(), get(), get()) }
addFactory { ResetMangaViewerFlags(get()) } addFactory { ResetMangaViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) } addFactory { SetMangaChapterFlags(get()) }
addFactory { SetMangaUpdateInterval(get()) } addFactory { SetMangaFetchInterval(get()) }
addFactory { addFactory {
SetMangaDefaultChapterFlags( SetMangaDefaultChapterFlags(
get(), get(),

View file

@ -3,7 +3,7 @@ package eu.kanade.domain.entries.anime.interactor
import eu.kanade.domain.entries.anime.model.hasCustomCover import eu.kanade.domain.entries.anime.model.hasCustomCover
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
import tachiyomi.domain.entries.anime.interactor.SetAnimeUpdateInterval import tachiyomi.domain.entries.anime.interactor.SetAnimeFetchInterval
import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.anime.model.Anime
import tachiyomi.domain.entries.anime.model.AnimeUpdate import tachiyomi.domain.entries.anime.model.AnimeUpdate
import tachiyomi.domain.entries.anime.repository.AnimeRepository import tachiyomi.domain.entries.anime.repository.AnimeRepository
@ -16,7 +16,7 @@ import java.util.Date
class UpdateAnime( class UpdateAnime(
private val animeRepository: AnimeRepository, private val animeRepository: AnimeRepository,
private val setAnimeUpdateInterval: SetAnimeUpdateInterval, private val setAnimeFetchInterval: SetAnimeFetchInterval,
) { ) {
suspend fun await(animeUpdate: AnimeUpdate): Boolean { suspend fun await(animeUpdate: AnimeUpdate): Boolean {
@ -81,9 +81,9 @@ class UpdateAnime(
anime: Anime, anime: Anime,
episodes: List<Episode>, episodes: List<Episode>,
zonedDateTime: ZonedDateTime = ZonedDateTime.now(), zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
fetchRange: Pair<Long, Long> = setAnimeUpdateInterval.getCurrentFetchRange(zonedDateTime), fetchRange: Pair<Long, Long> = setAnimeFetchInterval.getCurrent(zonedDateTime),
): Boolean { ): Boolean {
val updateAnime = setAnimeUpdateInterval.updateInterval(anime, episodes, zonedDateTime, fetchRange) val updateAnime = setAnimeFetchInterval.update(anime, episodes, zonedDateTime, fetchRange)
return if (updateAnime != null) { return if (updateAnime != null) {
animeRepository.updateAnime(updateAnime) animeRepository.updateAnime(updateAnime)
} else { } else {

View file

@ -3,7 +3,7 @@ package eu.kanade.domain.entries.manga.interactor
import eu.kanade.domain.entries.manga.model.hasCustomCover import eu.kanade.domain.entries.manga.model.hasCustomCover
import eu.kanade.tachiyomi.data.cache.MangaCoverCache import eu.kanade.tachiyomi.data.cache.MangaCoverCache
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import tachiyomi.domain.entries.manga.interactor.SetMangaUpdateInterval import tachiyomi.domain.entries.manga.interactor.SetMangaFetchInterval
import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.Manga
import tachiyomi.domain.entries.manga.model.MangaUpdate import tachiyomi.domain.entries.manga.model.MangaUpdate
import tachiyomi.domain.entries.manga.repository.MangaRepository import tachiyomi.domain.entries.manga.repository.MangaRepository
@ -16,7 +16,7 @@ import java.util.Date
class UpdateManga( class UpdateManga(
private val mangaRepository: MangaRepository, private val mangaRepository: MangaRepository,
private val setMangaUpdateInterval: SetMangaUpdateInterval, private val setMangaFetchInterval: SetMangaFetchInterval,
) { ) {
suspend fun await(mangaUpdate: MangaUpdate): Boolean { suspend fun await(mangaUpdate: MangaUpdate): Boolean {
@ -81,9 +81,9 @@ class UpdateManga(
manga: Manga, manga: Manga,
chapters: List<Chapter>, chapters: List<Chapter>,
zonedDateTime: ZonedDateTime = ZonedDateTime.now(), zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
fetchRange: Pair<Long, Long> = setMangaUpdateInterval.getCurrentFetchRange(zonedDateTime), fetchRange: Pair<Long, Long> = setMangaFetchInterval.getCurrent(zonedDateTime),
): Boolean { ): Boolean {
val updatedManga = setMangaUpdateInterval.updateInterval(manga, chapters, zonedDateTime, fetchRange) val updatedManga = setMangaFetchInterval.update(manga, chapters, zonedDateTime, fetchRange)
return if (updatedManga != null) { return if (updatedManga != null) {
mangaRepository.updateManga(updatedManga) mangaRepository.updateManga(updatedManga)
} else { } else {

View file

@ -138,7 +138,7 @@ class SyncChaptersWithSource(
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions. // Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) { if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
if (manualFetch || manga.calculateInterval == 0 || manga.nextUpdate < fetchRange.first) { if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchRange.first) {
updateManga.awaitUpdateFetchInterval( updateManga.awaitUpdateFetchInterval(
manga, manga,
dbChapters, dbChapters,

View file

@ -138,7 +138,7 @@ class SyncEpisodesWithSource(
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions. // Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) { if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
if (manualFetch || anime.calculateInterval == 0 || anime.nextUpdate < fetchRange.first) { if (manualFetch || anime.fetchInterval == 0 || anime.nextUpdate < fetchRange.first) {
updateAnime.awaitUpdateFetchInterval( updateAnime.awaitUpdateFetchInterval(
anime, anime,
dbEpisodes, dbEpisodes,

View file

@ -74,6 +74,7 @@ import eu.kanade.tachiyomi.source.anime.getNameForAnimeInfo
import eu.kanade.tachiyomi.ui.browse.anime.extension.details.SourcePreferencesScreen import eu.kanade.tachiyomi.ui.browse.anime.extension.details.SourcePreferencesScreen
import eu.kanade.tachiyomi.ui.entries.anime.AnimeScreenModel import eu.kanade.tachiyomi.ui.entries.anime.AnimeScreenModel
import eu.kanade.tachiyomi.ui.entries.anime.EpisodeItem import eu.kanade.tachiyomi.ui.entries.anime.EpisodeItem
import eu.kanade.tachiyomi.ui.entries.anime.FetchAnimeInterval
import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.lang.toRelativeString
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -97,7 +98,7 @@ import java.util.concurrent.TimeUnit
fun AnimeScreen( fun AnimeScreen(
state: AnimeScreenModel.State.Success, state: AnimeScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
intervalDisplay: () -> Pair<Int, Int>?, fetchInterval: FetchAnimeInterval?,
dateFormat: DateFormat, dateFormat: DateFormat,
isTabletUi: Boolean, isTabletUi: Boolean,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
@ -127,7 +128,7 @@ fun AnimeScreen(
onShareClicked: (() -> Unit)?, onShareClicked: (() -> Unit)?,
onDownloadActionClicked: ((DownloadAction) -> Unit)?, onDownloadActionClicked: ((DownloadAction) -> Unit)?,
onEditCategoryClicked: (() -> Unit)?, onEditCategoryClicked: (() -> Unit)?,
onEditIntervalClicked: (() -> Unit)?, onEditFetchIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?,
changeAnimeSkipIntro: (() -> Unit)?, changeAnimeSkipIntro: (() -> Unit)?,
@ -162,7 +163,7 @@ fun AnimeScreen(
state = state, state = state,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateFormat = dateFormat, dateFormat = dateFormat,
intervalDisplay = intervalDisplay, fetchInterval = fetchInterval,
episodeSwipeStartAction = episodeSwipeStartAction, episodeSwipeStartAction = episodeSwipeStartAction,
episodeSwipeEndAction = episodeSwipeEndAction, episodeSwipeEndAction = episodeSwipeEndAction,
showNextEpisodeAirTime = showNextEpisodeAirTime, showNextEpisodeAirTime = showNextEpisodeAirTime,
@ -184,7 +185,7 @@ fun AnimeScreen(
onShareClicked = onShareClicked, onShareClicked = onShareClicked,
onDownloadActionClicked = onDownloadActionClicked, onDownloadActionClicked = onDownloadActionClicked,
onEditCategoryClicked = onEditCategoryClicked, onEditCategoryClicked = onEditCategoryClicked,
onEditIntervalClicked = onEditIntervalClicked, onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked, onMigrateClicked = onMigrateClicked,
changeAnimeSkipIntro = changeAnimeSkipIntro, changeAnimeSkipIntro = changeAnimeSkipIntro,
onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiBookmarkClicked = onMultiBookmarkClicked,
@ -206,7 +207,7 @@ fun AnimeScreen(
showNextEpisodeAirTime = showNextEpisodeAirTime, showNextEpisodeAirTime = showNextEpisodeAirTime,
alwaysUseExternalPlayer = alwaysUseExternalPlayer, alwaysUseExternalPlayer = alwaysUseExternalPlayer,
dateFormat = dateFormat, dateFormat = dateFormat,
intervalDisplay = intervalDisplay, fetchInterval = fetchInterval,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
onEpisodeClicked = onEpisodeClicked, onEpisodeClicked = onEpisodeClicked,
onDownloadEpisode = onDownloadEpisode, onDownloadEpisode = onDownloadEpisode,
@ -224,7 +225,7 @@ fun AnimeScreen(
onShareClicked = onShareClicked, onShareClicked = onShareClicked,
onDownloadActionClicked = onDownloadActionClicked, onDownloadActionClicked = onDownloadActionClicked,
onEditCategoryClicked = onEditCategoryClicked, onEditCategoryClicked = onEditCategoryClicked,
onEditIntervalClicked = onEditIntervalClicked, onEditIntervalClicked = onEditFetchIntervalClicked,
changeAnimeSkipIntro = changeAnimeSkipIntro, changeAnimeSkipIntro = changeAnimeSkipIntro,
onMigrateClicked = onMigrateClicked, onMigrateClicked = onMigrateClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiBookmarkClicked = onMultiBookmarkClicked,
@ -246,7 +247,7 @@ private fun AnimeScreenSmallImpl(
state: AnimeScreenModel.State.Success, state: AnimeScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateFormat: DateFormat, dateFormat: DateFormat,
intervalDisplay: () -> Pair<Int, Int>?, fetchInterval: FetchAnimeInterval?,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
showNextEpisodeAirTime: Boolean, showNextEpisodeAirTime: Boolean,
@ -432,8 +433,8 @@ private fun AnimeScreenSmallImpl(
AnimeActionRow( AnimeActionRow(
favorite = state.anime.favorite, favorite = state.anime.favorite,
trackingCount = state.trackingCount, trackingCount = state.trackingCount,
intervalDisplay = intervalDisplay, fetchInterval = fetchInterval,
isUserIntervalMode = state.anime.calculateInterval < 0, isUserIntervalMode = state.anime.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked, onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked, onWebViewClicked = onWebViewClicked,
onWebViewLongClicked = onWebViewLongClicked, onWebViewLongClicked = onWebViewLongClicked,
@ -517,7 +518,7 @@ fun AnimeScreenLargeImpl(
state: AnimeScreenModel.State.Success, state: AnimeScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateFormat: DateFormat, dateFormat: DateFormat,
intervalDisplay: () -> Pair<Int, Int>?, fetchInterval: FetchAnimeInterval?,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
showNextEpisodeAirTime: Boolean, showNextEpisodeAirTime: Boolean,
@ -685,8 +686,8 @@ fun AnimeScreenLargeImpl(
AnimeActionRow( AnimeActionRow(
favorite = state.anime.favorite, favorite = state.anime.favorite,
trackingCount = state.trackingCount, trackingCount = state.trackingCount,
intervalDisplay = intervalDisplay, fetchInterval = fetchInterval,
isUserIntervalMode = state.anime.calculateInterval < 0, isUserIntervalMode = state.anime.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked, onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked, onWebViewClicked = onWebViewClicked,
onWebViewLongClicked = onWebViewLongClicked, onWebViewLongClicked = onWebViewLongClicked,

View file

@ -1,6 +1,7 @@
package eu.kanade.presentation.entries.anime package eu.kanade.presentation.entries.anime
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -8,6 +9,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@Composable @Composable
@ -18,15 +20,27 @@ fun DuplicateAnimeDialog(
) { ) {
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = {
Text(text = stringResource(R.string.are_you_sure))
},
text = {
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
},
confirmButton = { confirmButton = {
Row { FlowRow(
TextButton(onClick = { horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
TextButton(
onClick = {
onDismissRequest() onDismissRequest()
onOpenAnime() onOpenAnime()
},) { },
) {
Text(text = stringResource(R.string.action_show_anime)) Text(text = stringResource(R.string.action_show_anime))
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onDismissRequest) { TextButton(onClick = onDismissRequest) {
Text(text = stringResource(R.string.action_cancel)) Text(text = stringResource(R.string.action_cancel))
} }
@ -40,11 +54,5 @@ fun DuplicateAnimeDialog(
} }
} }
}, },
title = {
Text(text = stringResource(R.string.are_you_sure))
},
text = {
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
},
) )
} }

View file

@ -80,6 +80,7 @@ import eu.kanade.presentation.entries.DotSeparatorText
import eu.kanade.presentation.entries.ItemCover import eu.kanade.presentation.entries.ItemCover
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.ui.entries.anime.FetchAnimeInterval
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.anime.model.Anime
import tachiyomi.presentation.core.components.material.TextButton import tachiyomi.presentation.core.components.material.TextButton
@ -167,7 +168,7 @@ fun AnimeActionRow(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
favorite: Boolean, favorite: Boolean,
trackingCount: Int, trackingCount: Int,
intervalDisplay: () -> Pair<Int, Int>?, fetchInterval: FetchAnimeInterval?,
isUserIntervalMode: Boolean, isUserIntervalMode: Boolean,
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
@ -176,7 +177,6 @@ fun AnimeActionRow(
onEditIntervalClicked: (() -> Unit)?, onEditIntervalClicked: (() -> Unit)?,
onEditCategory: (() -> Unit)?, onEditCategory: (() -> Unit)?,
) { ) {
val interval: Pair<Int, Int>? = intervalDisplay()
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f) val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) { Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
@ -191,13 +191,14 @@ fun AnimeActionRow(
onClick = onAddToLibraryClicked, onClick = onAddToLibraryClicked,
onLongClick = onEditCategory, onLongClick = onEditCategory,
) )
if (onEditIntervalClicked != null && interval != null) { if (onEditIntervalClicked != null && fetchInterval != null) {
val intervalPair = 1.coerceAtLeast(fetchInterval.interval - fetchInterval.leadDays) to (fetchInterval.interval + fetchInterval.followDays)
AnimeActionButton( AnimeActionButton(
title = title =
if (interval.first == interval.second) { if (intervalPair.first == intervalPair.second) {
pluralStringResource(id = R.plurals.day, count = interval.second, interval.second) pluralStringResource(id = R.plurals.day, count = intervalPair.second, intervalPair.second)
} else { } else {
pluralStringResource(id = R.plurals.range_interval_day, count = interval.second, interval.first, interval.second) pluralStringResource(id = R.plurals.range_interval_day, count = intervalPair.second, intervalPair.first, intervalPair.second)
}, },
icon = Icons.Default.HourglassEmpty, icon = Icons.Default.HourglassEmpty,
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor, color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,

View file

@ -1,6 +1,7 @@
package eu.kanade.presentation.entries.manga package eu.kanade.presentation.entries.manga
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -8,6 +9,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@Composable @Composable
@ -18,15 +20,27 @@ fun DuplicateMangaDialog(
) { ) {
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = {
Text(text = stringResource(R.string.are_you_sure))
},
text = {
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
},
confirmButton = { confirmButton = {
Row { FlowRow(
TextButton(onClick = { horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
TextButton(
onClick = {
onDismissRequest() onDismissRequest()
onOpenManga() onOpenManga()
},) { },
) {
Text(text = stringResource(R.string.action_show_manga)) Text(text = stringResource(R.string.action_show_manga))
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onDismissRequest) { TextButton(onClick = onDismissRequest) {
Text(text = stringResource(R.string.action_cancel)) Text(text = stringResource(R.string.action_cancel))
} }
@ -40,11 +54,5 @@ fun DuplicateMangaDialog(
} }
} }
}, },
title = {
Text(text = stringResource(R.string.are_you_sure))
},
text = {
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
},
) )
} }

View file

@ -68,6 +68,7 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.manga.getNameForMangaInfo import eu.kanade.tachiyomi.source.manga.getNameForMangaInfo
import eu.kanade.tachiyomi.ui.browse.manga.extension.details.MangaSourcePreferencesScreen import eu.kanade.tachiyomi.ui.browse.manga.extension.details.MangaSourcePreferencesScreen
import eu.kanade.tachiyomi.ui.entries.manga.ChapterItem import eu.kanade.tachiyomi.ui.entries.manga.ChapterItem
import eu.kanade.tachiyomi.ui.entries.manga.FetchMangaInterval
import eu.kanade.tachiyomi.ui.entries.manga.MangaScreenModel import eu.kanade.tachiyomi.ui.entries.manga.MangaScreenModel
import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.lang.toRelativeString
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
@ -90,7 +91,7 @@ import java.util.Date
fun MangaScreen( fun MangaScreen(
state: MangaScreenModel.State.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
intervalDisplay: () -> Pair<Int, Int>?, fetchInterval: FetchMangaInterval?,
dateFormat: DateFormat, dateFormat: DateFormat,
isTabletUi: Boolean, isTabletUi: Boolean,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
@ -118,7 +119,7 @@ fun MangaScreen(
onShareClicked: (() -> Unit)?, onShareClicked: (() -> Unit)?,
onDownloadActionClicked: ((DownloadAction) -> Unit)?, onDownloadActionClicked: ((DownloadAction) -> Unit)?,
onEditCategoryClicked: (() -> Unit)?, onEditCategoryClicked: (() -> Unit)?,
onEditIntervalClicked: (() -> Unit)?, onEditFetchIntervalClicked: (() -> Unit)?,
onMigrateClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?,
// For bottom action menu // For bottom action menu
@ -152,7 +153,7 @@ fun MangaScreen(
state = state, state = state,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateFormat = dateFormat, dateFormat = dateFormat,
intervalDisplay = intervalDisplay, fetchInterval = fetchInterval,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
@ -172,7 +173,7 @@ fun MangaScreen(
onShareClicked = onShareClicked, onShareClicked = onShareClicked,
onDownloadActionClicked = onDownloadActionClicked, onDownloadActionClicked = onDownloadActionClicked,
onEditCategoryClicked = onEditCategoryClicked, onEditCategoryClicked = onEditCategoryClicked,
onEditIntervalClicked = onEditIntervalClicked, onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked, onMigrateClicked = onMigrateClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
@ -191,7 +192,7 @@ fun MangaScreen(
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
dateFormat = dateFormat, dateFormat = dateFormat,
intervalDisplay = intervalDisplay, fetchInterval = fetchInterval,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter, onDownloadChapter = onDownloadChapter,
@ -209,7 +210,7 @@ fun MangaScreen(
onShareClicked = onShareClicked, onShareClicked = onShareClicked,
onDownloadActionClicked = onDownloadActionClicked, onDownloadActionClicked = onDownloadActionClicked,
onEditCategoryClicked = onEditCategoryClicked, onEditCategoryClicked = onEditCategoryClicked,
onEditIntervalClicked = onEditIntervalClicked, onEditIntervalClicked = onEditFetchIntervalClicked,
onMigrateClicked = onMigrateClicked, onMigrateClicked = onMigrateClicked,
onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
@ -229,7 +230,7 @@ private fun MangaScreenSmallImpl(
state: MangaScreenModel.State.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateFormat: DateFormat, dateFormat: DateFormat,
intervalDisplay: () -> Pair<Int, Int>?, fetchInterval: FetchMangaInterval?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
@ -406,8 +407,8 @@ private fun MangaScreenSmallImpl(
MangaActionRow( MangaActionRow(
favorite = state.manga.favorite, favorite = state.manga.favorite,
trackingCount = state.trackingCount, trackingCount = state.trackingCount,
intervalDisplay = intervalDisplay, fetchInterval = fetchInterval,
isUserIntervalMode = state.manga.calculateInterval < 0, isUserIntervalMode = state.manga.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked, onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked, onWebViewClicked = onWebViewClicked,
onWebViewLongClicked = onWebViewLongClicked, onWebViewLongClicked = onWebViewLongClicked,
@ -465,7 +466,7 @@ fun MangaScreenLargeImpl(
state: MangaScreenModel.State.Success, state: MangaScreenModel.State.Success,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateFormat: DateFormat, dateFormat: DateFormat,
intervalDisplay: () -> Pair<Int, Int>?, fetchInterval: FetchMangaInterval?,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
@ -627,8 +628,8 @@ fun MangaScreenLargeImpl(
MangaActionRow( MangaActionRow(
favorite = state.manga.favorite, favorite = state.manga.favorite,
trackingCount = state.trackingCount, trackingCount = state.trackingCount,
intervalDisplay = intervalDisplay, fetchInterval = fetchInterval,
isUserIntervalMode = state.manga.calculateInterval < 0, isUserIntervalMode = state.manga.fetchInterval < 0,
onAddToLibraryClicked = onAddToLibraryClicked, onAddToLibraryClicked = onAddToLibraryClicked,
onWebViewClicked = onWebViewClicked, onWebViewClicked = onWebViewClicked,
onWebViewLongClicked = onWebViewLongClicked, onWebViewLongClicked = onWebViewLongClicked,

View file

@ -80,6 +80,7 @@ import eu.kanade.presentation.entries.DotSeparatorText
import eu.kanade.presentation.entries.ItemCover import eu.kanade.presentation.entries.ItemCover
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.entries.manga.FetchMangaInterval
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.Manga
import tachiyomi.presentation.core.components.material.TextButton import tachiyomi.presentation.core.components.material.TextButton
@ -167,7 +168,7 @@ fun MangaActionRow(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
favorite: Boolean, favorite: Boolean,
trackingCount: Int, trackingCount: Int,
intervalDisplay: () -> Pair<Int, Int>?, fetchInterval: FetchMangaInterval?,
isUserIntervalMode: Boolean, isUserIntervalMode: Boolean,
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
@ -176,7 +177,6 @@ fun MangaActionRow(
onEditIntervalClicked: (() -> Unit)?, onEditIntervalClicked: (() -> Unit)?,
onEditCategory: (() -> Unit)?, onEditCategory: (() -> Unit)?,
) { ) {
val interval: Pair<Int, Int>? = intervalDisplay()
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f) val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) { Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
@ -191,13 +191,14 @@ fun MangaActionRow(
onClick = onAddToLibraryClicked, onClick = onAddToLibraryClicked,
onLongClick = onEditCategory, onLongClick = onEditCategory,
) )
if (onEditIntervalClicked != null && interval != null) { if (onEditIntervalClicked != null && fetchInterval != null) {
val intervalPair = 1.coerceAtLeast(fetchInterval.interval - fetchInterval.leadDays) to (fetchInterval.interval + fetchInterval.followDays)
MangaActionButton( MangaActionButton(
title = title =
if (interval.first == interval.second) { if (intervalPair.first == intervalPair.second) {
pluralStringResource(id = R.plurals.day, count = interval.second, interval.second) pluralStringResource(id = R.plurals.day, count = intervalPair.second, intervalPair.second)
} else { } else {
pluralStringResource(id = R.plurals.range_interval_day, count = interval.second, interval.first, interval.second) pluralStringResource(id = R.plurals.range_interval_day, count = intervalPair.second, intervalPair.first, intervalPair.second)
}, },
icon = Icons.Default.HourglassEmpty, icon = Icons.Default.HourglassEmpty,
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor, color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,

View file

@ -235,6 +235,7 @@ object SettingsAdvancedScreen : SearchableSettings {
onClick = { onClick = {
Injekt.get<MangaDownloadCache>().invalidateCache() Injekt.get<MangaDownloadCache>().invalidateCache()
Injekt.get<AnimeDownloadCache>().invalidateCache() Injekt.get<AnimeDownloadCache>().invalidateCache()
context.toast(R.string.download_cache_invalidated)
}, },
), ),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(

View file

@ -5,11 +5,16 @@ import android.os.Build
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import com.squareup.sqldelight.android.AndroidSqliteDriver import app.cash.sqldelight.adapter.primitive.FloatColumnAdapter
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import data.Chapters
import data.History import data.History
import data.Manga_sync
import data.Mangas import data.Mangas
import dataanime.Anime_sync
import dataanime.Animehistory import dataanime.Animehistory
import dataanime.Animes import dataanime.Animes
import dataanime.Episodes
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.anime.store.DelayedAnimeTrackingStore import eu.kanade.domain.track.anime.store.DelayedAnimeTrackingStore
@ -131,9 +136,15 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { addSingletonFactory {
Database( Database(
driver = sqlDriverManga, driver = sqlDriverManga,
chaptersAdapter = Chapters.Adapter(
chapter_numberAdapter = FloatColumnAdapter,
),
historyAdapter = History.Adapter( historyAdapter = History.Adapter(
last_readAdapter = dateAdapter, last_readAdapter = dateAdapter,
), ),
manga_syncAdapter = Manga_sync.Adapter(
scoreAdapter = FloatColumnAdapter,
),
mangasAdapter = Mangas.Adapter( mangasAdapter = Mangas.Adapter(
genreAdapter = listOfStringsAdapter, genreAdapter = listOfStringsAdapter,
update_strategyAdapter = updateStrategyAdapter, update_strategyAdapter = updateStrategyAdapter,
@ -144,9 +155,15 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { addSingletonFactory {
AnimeDatabase( AnimeDatabase(
driver = sqlDriverAnime, driver = sqlDriverAnime,
episodesAdapter = Episodes.Adapter(
episode_numberAdapter = FloatColumnAdapter,
),
animehistoryAdapter = Animehistory.Adapter( animehistoryAdapter = Animehistory.Adapter(
last_seenAdapter = dateAdapter, last_seenAdapter = dateAdapter,
), ),
anime_syncAdapter = Anime_sync.Adapter(
scoreAdapter = FloatColumnAdapter,
),
animesAdapter = Animes.Adapter( animesAdapter = Animes.Adapter(
genreAdapter = listOfStringsAdapter, genreAdapter = listOfStringsAdapter,
update_strategyAdapter = updateStrategyAdapter, update_strategyAdapter = updateStrategyAdapter,

View file

@ -511,7 +511,7 @@ class BackupManager(
} }
if (!found) { if (!found) {
// Let the db assign the id // Let the db assign the id
val id = mangaHandler.awaitOne { val id = mangaHandler.awaitOneExecutable {
categoriesQueries.insert(category.name, category.order, category.flags) categoriesQueries.insert(category.name, category.order, category.flags)
categoriesQueries.selectLastInsertedRowId() categoriesQueries.selectLastInsertedRowId()
} }
@ -551,7 +551,7 @@ class BackupManager(
} }
if (!found) { if (!found) {
// Let the db assign the id // Let the db assign the id
val id = animeHandler.awaitOne { val id = animeHandler.awaitOneExecutable {
categoriesQueries.insert(category.name, category.order, category.flags) categoriesQueries.insert(category.name, category.order, category.flags)
categoriesQueries.selectLastInsertedRowId() categoriesQueries.selectLastInsertedRowId()
} }
@ -970,7 +970,7 @@ class BackupManager(
* @return id of [Manga], null if not found * @return id of [Manga], null if not found
*/ */
private suspend fun insertManga(manga: Manga): Long { private suspend fun insertManga(manga: Manga): Long {
return mangaHandler.awaitOne(true) { return mangaHandler.awaitOneExecutable(true) {
mangasQueries.insert( mangasQueries.insert(
source = manga.source, source = manga.source,
url = manga.url, url = manga.url,
@ -1002,7 +1002,7 @@ class BackupManager(
* @return id of [Anime], null if not found * @return id of [Anime], null if not found
*/ */
private suspend fun insertAnime(anime: Anime): Long { private suspend fun insertAnime(anime: Anime): Long {
return animeHandler.awaitOne(true) { return animeHandler.awaitOneExecutable(true) {
animesQueries.insert( animesQueries.insert(
source = anime.source, source = anime.source,
url = anime.url, url = anime.url,
@ -1040,11 +1040,11 @@ class BackupManager(
title = manga.title, title = manga.title,
status = manga.status, status = manga.status,
thumbnailUrl = manga.thumbnailUrl, thumbnailUrl = manga.thumbnailUrl,
favorite = manga.favorite.toLong(), favorite = manga.favorite,
lastUpdate = manga.lastUpdate, lastUpdate = manga.lastUpdate,
nextUpdate = null, nextUpdate = null,
calculateInterval = null, calculateInterval = null,
initialized = manga.initialized.toLong(), initialized = manga.initialized,
viewer = manga.viewerFlags, viewer = manga.viewerFlags,
chapterFlags = manga.chapterFlags, chapterFlags = manga.chapterFlags,
coverLastModified = manga.coverLastModified, coverLastModified = manga.coverLastModified,
@ -1068,11 +1068,11 @@ class BackupManager(
title = anime.title, title = anime.title,
status = anime.status, status = anime.status,
thumbnailUrl = anime.thumbnailUrl, thumbnailUrl = anime.thumbnailUrl,
favorite = anime.favorite.toLong(), favorite = anime.favorite,
lastUpdate = anime.lastUpdate, lastUpdate = anime.lastUpdate,
nextUpdate = null, nextUpdate = null,
calculateInterval = null, calculateInterval = null,
initialized = anime.initialized.toLong(), initialized = anime.initialized,
viewer = anime.viewerFlags, viewer = anime.viewerFlags,
episodeFlags = anime.episodeFlags, episodeFlags = anime.episodeFlags,
coverLastModified = anime.coverLastModified, coverLastModified = anime.coverLastModified,
@ -1142,8 +1142,8 @@ class BackupManager(
url = null, url = null,
name = null, name = null,
scanlator = null, scanlator = null,
read = chapter.read.toLong(), read = chapter.read,
bookmark = chapter.bookmark.toLong(), bookmark = chapter.bookmark,
lastPageRead = chapter.lastPageRead, lastPageRead = chapter.lastPageRead,
chapterNumber = null, chapterNumber = null,
sourceOrder = null, sourceOrder = null,
@ -1166,8 +1166,8 @@ class BackupManager(
url = null, url = null,
name = null, name = null,
scanlator = null, scanlator = null,
seen = episode.seen.toLong(), seen = episode.seen,
bookmark = episode.bookmark.toLong(), bookmark = episode.bookmark,
lastSecondSeen = episode.lastSecondSeen, lastSecondSeen = episode.lastSecondSeen,
totalSeconds = episode.totalSeconds, totalSeconds = episode.totalSeconds,
episodeNumber = null, episodeNumber = null,

View file

@ -30,9 +30,9 @@ import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.entries.anime.interactor.SetAnimeUpdateInterval import tachiyomi.domain.entries.anime.interactor.SetAnimeFetchInterval
import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.anime.model.Anime
import tachiyomi.domain.entries.manga.interactor.SetMangaUpdateInterval import tachiyomi.domain.entries.manga.interactor.SetMangaFetchInterval
import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.Manga
import tachiyomi.domain.items.chapter.model.Chapter import tachiyomi.domain.items.chapter.model.Chapter
import tachiyomi.domain.items.chapter.repository.ChapterRepository import tachiyomi.domain.items.chapter.repository.ChapterRepository
@ -54,15 +54,15 @@ class BackupRestorer(
) { ) {
private val updateManga: UpdateManga = Injekt.get() private val updateManga: UpdateManga = Injekt.get()
private val chapterRepository: ChapterRepository = Injekt.get() private val chapterRepository: ChapterRepository = Injekt.get()
private val setMangaUpdateInterval: SetMangaUpdateInterval = Injekt.get() private val setMangaFetchInterval: SetMangaFetchInterval = Injekt.get()
private val updateAnime: UpdateAnime = Injekt.get() private val updateAnime: UpdateAnime = Injekt.get()
private val episodeRepository: EpisodeRepository = Injekt.get() private val episodeRepository: EpisodeRepository = Injekt.get()
private val setAnimeUpdateInterval: SetAnimeUpdateInterval = Injekt.get() private val setAnimeFetchInterval: SetAnimeFetchInterval = Injekt.get()
private var zonedDateTime = ZonedDateTime.now() private var zonedDateTime = ZonedDateTime.now()
private var currentMangaRange = setMangaUpdateInterval.getCurrentFetchRange(zonedDateTime) private var currentMangaFetchInterval = setMangaFetchInterval.getCurrent(zonedDateTime)
private var currentAnimeRange = setAnimeUpdateInterval.getCurrentFetchRange(zonedDateTime) private var currentAnimeFetchInterval = setAnimeFetchInterval.getCurrent(zonedDateTime)
private var backupManager = BackupManager(context) private var backupManager = BackupManager(context)
@ -141,8 +141,8 @@ class BackupRestorer(
animeSourceMapping = backupAnimeMaps.associate { it.sourceId to it.name } animeSourceMapping = backupAnimeMaps.associate { it.sourceId to it.name }
zonedDateTime = ZonedDateTime.now() zonedDateTime = ZonedDateTime.now()
currentMangaRange = setMangaUpdateInterval.getCurrentFetchRange(zonedDateTime) currentMangaFetchInterval = setMangaFetchInterval.getCurrent(zonedDateTime)
currentAnimeRange = setAnimeUpdateInterval.getCurrentFetchRange(zonedDateTime) currentAnimeFetchInterval = setAnimeFetchInterval.getCurrent(zonedDateTime)
return coroutineScope { return coroutineScope {
// Restore individual manga // Restore individual manga
@ -217,7 +217,7 @@ class BackupRestorer(
restoreNewManga(updateManga, chapters, categories, history, tracks, backupCategories) restoreNewManga(updateManga, chapters, categories, history, tracks, backupCategories)
} }
val updatedChapters = chapterRepository.getChapterByMangaId(restoredManga.id) val updatedChapters = chapterRepository.getChapterByMangaId(restoredManga.id)
updateManga.awaitUpdateFetchInterval(restoredManga, updatedChapters, zonedDateTime, currentMangaRange) updateManga.awaitUpdateFetchInterval(restoredManga, updatedChapters, zonedDateTime, currentMangaFetchInterval)
} catch (e: Exception) { } catch (e: Exception) {
val sourceName = sourceMapping[manga.source] ?: manga.source.toString() val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}") errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
@ -292,7 +292,7 @@ class BackupRestorer(
restoreNewAnime(updateAnime, episodes, categories, history, tracks, backupCategories) restoreNewAnime(updateAnime, episodes, categories, history, tracks, backupCategories)
} }
val updatedEpisodes = episodeRepository.getEpisodeByAnimeId(restoredAnime.id) val updatedEpisodes = episodeRepository.getEpisodeByAnimeId(restoredAnime.id)
updateAnime.awaitUpdateFetchInterval(restoredAnime, updatedEpisodes, zonedDateTime, currentAnimeRange) updateAnime.awaitUpdateFetchInterval(restoredAnime, updatedEpisodes, zonedDateTime, currentAnimeFetchInterval)
} catch (e: Exception) { } catch (e: Exception) {
val sourceName = sourceMapping[anime.source] ?: anime.source.toString() val sourceName = sourceMapping[anime.source] ?: anime.source.toString()
errors.add(Date() to "${anime.title} [$sourceName]: ${e.message}") errors.add(Date() to "${anime.title} [$sourceName]: ${e.message}")

View file

@ -56,7 +56,7 @@ import tachiyomi.domain.category.model.Category
import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.interactor.GetAnime
import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime
import tachiyomi.domain.entries.anime.interactor.SetAnimeUpdateInterval import tachiyomi.domain.entries.anime.interactor.SetAnimeFetchInterval
import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.anime.model.Anime
import tachiyomi.domain.entries.anime.model.toAnimeUpdate import tachiyomi.domain.entries.anime.model.toAnimeUpdate
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
@ -104,7 +104,7 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
private val getTracks: GetAnimeTracks = Injekt.get() private val getTracks: GetAnimeTracks = Injekt.get()
private val insertTrack: InsertAnimeTrack = Injekt.get() private val insertTrack: InsertAnimeTrack = Injekt.get()
private val syncEpisodesWithTrackServiceTwoWay: SyncEpisodesWithTrackServiceTwoWay = Injekt.get() private val syncEpisodesWithTrackServiceTwoWay: SyncEpisodesWithTrackServiceTwoWay = Injekt.get()
private val setAnimeUpdateInterval: SetAnimeUpdateInterval = Injekt.get() private val setAnimeFetchInterval: SetAnimeFetchInterval = Injekt.get()
private val notifier = AnimeLibraryUpdateNotifier(context) private val notifier = AnimeLibraryUpdateNotifier(context)
@ -232,8 +232,8 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
val restrictions = libraryPreferences.libraryUpdateItemRestriction().get() val restrictions = libraryPreferences.libraryUpdateItemRestriction().get()
val now = ZonedDateTime.now() val now = ZonedDateTime.now()
val fetchRange = setAnimeUpdateInterval.getCurrentFetchRange(now) val fetchInterval = setAnimeFetchInterval.getCurrent(now)
val higherLimit = fetchRange.second val higherLimit = fetchInterval.second
coroutineScope { coroutineScope {
animeToUpdate.groupBy { it.anime.source }.values animeToUpdate.groupBy { it.anime.source }.values
@ -272,7 +272,7 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
else -> { else -> {
try { try {
val newEpisodes = updateAnime(anime, now, fetchRange) val newEpisodes = updateAnime(anime, now, fetchInterval)
.sortedByDescending { it.sourceOrder } .sortedByDescending { it.sourceOrder }
if (newEpisodes.isNotEmpty()) { if (newEpisodes.isNotEmpty()) {

View file

@ -56,7 +56,7 @@ import tachiyomi.domain.category.model.Category
import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.entries.manga.interactor.GetLibraryManga import tachiyomi.domain.entries.manga.interactor.GetLibraryManga
import tachiyomi.domain.entries.manga.interactor.GetManga import tachiyomi.domain.entries.manga.interactor.GetManga
import tachiyomi.domain.entries.manga.interactor.SetMangaUpdateInterval import tachiyomi.domain.entries.manga.interactor.SetMangaFetchInterval
import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.Manga
import tachiyomi.domain.entries.manga.model.toMangaUpdate import tachiyomi.domain.entries.manga.model.toMangaUpdate
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
@ -104,7 +104,7 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
private val getTracks: GetMangaTracks = Injekt.get() private val getTracks: GetMangaTracks = Injekt.get()
private val insertTrack: InsertMangaTrack = Injekt.get() private val insertTrack: InsertMangaTrack = Injekt.get()
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get() private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get()
private val setMangaUpdateInterval: SetMangaUpdateInterval = Injekt.get() private val setMangaFetchInterval: SetMangaFetchInterval = Injekt.get()
private val notifier = MangaLibraryUpdateNotifier(context) private val notifier = MangaLibraryUpdateNotifier(context)
@ -232,8 +232,8 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
val restrictions = libraryPreferences.libraryUpdateItemRestriction().get() val restrictions = libraryPreferences.libraryUpdateItemRestriction().get()
val now = ZonedDateTime.now() val now = ZonedDateTime.now()
val fetchRange = setMangaUpdateInterval.getCurrentFetchRange(now) val fetchInterval = setMangaFetchInterval.getCurrent(now)
val higherLimit = fetchRange.second val higherLimit = fetchInterval.second
coroutineScope { coroutineScope {
mangaToUpdate.groupBy { it.manga.source }.values mangaToUpdate.groupBy { it.manga.source }.values
@ -272,7 +272,7 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa
else -> { else -> {
try { try {
val newChapters = updateManga(manga, now, fetchRange) val newChapters = updateManga(manga, now, fetchInterval)
.sortedByDescending { it.sourceOrder } .sortedByDescending { it.sourceOrder }
if (newChapters.isNotEmpty()) { if (newChapters.isNotEmpty()) {

View file

@ -121,8 +121,8 @@ internal fun MigrateAnimeDialog(
) { ) {
TextButton( TextButton(
onClick = { onClick = {
onClickTitle()
onDismissRequest() onDismissRequest()
onClickTitle()
}, },
) { ) {
Text(text = stringResource(R.string.action_show_anime)) Text(text = stringResource(R.string.action_show_anime))

View file

@ -329,7 +329,7 @@ class BrowseAnimeSourceScreenModel(
} }
suspend fun getDuplicateAnimelibAnime(anime: Anime): Anime? { suspend fun getDuplicateAnimelibAnime(anime: Anime): Anime? {
return getDuplicateAnimelibAnime.await(anime.title) return getDuplicateAnimelibAnime.await(anime).getOrNull(0)
} }
private fun moveAnimeToCategories(anime: Anime, vararg categories: Category) { private fun moveAnimeToCategories(anime: Anime, vararg categories: Category) {

View file

@ -121,8 +121,8 @@ internal fun MigrateMangaDialog(
) { ) {
TextButton( TextButton(
onClick = { onClick = {
onClickTitle()
onDismissRequest() onDismissRequest()
onClickTitle()
}, },
) { ) {
Text(text = stringResource(R.string.action_show_manga)) Text(text = stringResource(R.string.action_show_manga))

View file

@ -323,7 +323,7 @@ class BrowseMangaSourceScreenModel(
} }
suspend fun getDuplicateLibraryManga(manga: Manga): Manga? { suspend fun getDuplicateLibraryManga(manga: Manga): Manga? {
return getDuplicateLibraryManga.await(manga.title) return getDuplicateLibraryManga.await(manga).getOrNull(0)
} }
private fun moveMangaToCategories(manga: Manga, vararg categories: Category) { private fun moveMangaToCategories(manga: Manga, vararg categories: Category) {

View file

@ -89,6 +89,13 @@ class AnimeScreen(
val successState = state as AnimeScreenModel.State.Success val successState = state as AnimeScreenModel.State.Success
val isAnimeHttpSource = remember { successState.source is AnimeHttpSource } val isAnimeHttpSource = remember { successState.source is AnimeHttpSource }
val fetchInterval = remember(successState.anime.fetchInterval) {
FetchAnimeInterval(
interval = successState.anime.fetchInterval,
leadDays = screenModel.leadDay,
followDays = screenModel.followDay,
)
}
LaunchedEffect(successState.anime, screenModel.source) { LaunchedEffect(successState.anime, screenModel.source) {
if (isAnimeHttpSource) { if (isAnimeHttpSource) {
@ -106,7 +113,7 @@ class AnimeScreen(
state = successState, state = successState,
snackbarHostState = screenModel.snackbarHostState, snackbarHostState = screenModel.snackbarHostState,
dateFormat = screenModel.dateFormat, dateFormat = screenModel.dateFormat,
intervalDisplay = screenModel::intervalDisplay, fetchInterval = fetchInterval,
isTabletUi = isTabletUi(), isTabletUi = isTabletUi(),
episodeSwipeStartAction = screenModel.episodeSwipeStartAction, episodeSwipeStartAction = screenModel.episodeSwipeStartAction,
episodeSwipeEndAction = screenModel.episodeSwipeEndAction, episodeSwipeEndAction = screenModel.episodeSwipeEndAction,
@ -141,7 +148,7 @@ class AnimeScreen(
onShareClicked = { shareAnime(context, screenModel.anime, screenModel.source) }.takeIf { isAnimeHttpSource }, onShareClicked = { shareAnime(context, screenModel.anime, screenModel.source) }.takeIf { isAnimeHttpSource },
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() }, onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.anime.favorite }, onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.anime.favorite },
onEditIntervalClicked = screenModel::showSetAnimeIntervalDialog.takeIf { screenModel.isIntervalEnabled && successState.anime.favorite }, onEditFetchIntervalClicked = screenModel::showSetAnimeFetchIntervalDialog.takeIf { screenModel.isUpdateIntervalEnabled && successState.anime.favorite },
onMigrateClicked = { navigator.push(MigrateAnimeSearchScreen(successState.anime.id)) }.takeIf { successState.anime.favorite }, onMigrateClicked = { navigator.push(MigrateAnimeSearchScreen(successState.anime.id)) }.takeIf { successState.anime.favorite },
changeAnimeSkipIntro = screenModel::showAnimeSkipIntroDialog.takeIf { successState.anime.favorite }, changeAnimeSkipIntro = screenModel::showAnimeSkipIntroDialog.takeIf { successState.anime.favorite },
onMultiBookmarkClicked = screenModel::bookmarkEpisodes, onMultiBookmarkClicked = screenModel::bookmarkEpisodes,
@ -235,11 +242,11 @@ class AnimeScreen(
LoadingScreen(Modifier.systemBarsPadding()) LoadingScreen(Modifier.systemBarsPadding())
} }
} }
is AnimeScreenModel.Dialog.SetAnimeInterval -> { is AnimeScreenModel.Dialog.SetAnimeFetchInterval -> {
SetIntervalDialog( SetIntervalDialog(
interval = if (dialog.anime.calculateInterval < 0) -dialog.anime.calculateInterval else 0, interval = if (dialog.anime.fetchInterval < 0) -dialog.anime.fetchInterval else 0,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onValueChanged = { screenModel.setFetchRangeInterval(dialog.anime, it) }, onValueChanged = { screenModel.setFetchInterval(dialog.anime, it) },
) )
} }
AnimeScreenModel.Dialog.ChangeAnimeSkipIntro -> { AnimeScreenModel.Dialog.ChangeAnimeSkipIntro -> {

View file

@ -79,7 +79,6 @@ import tachiyomi.source.local.entries.anime.isLocal
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Calendar import java.util.Calendar
import kotlin.math.absoluteValue
class AnimeScreenModel( class AnimeScreenModel(
val context: Context, val context: Context,
@ -135,9 +134,9 @@ class AnimeScreenModel(
val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
val isIntervalEnabled = LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in libraryPreferences.libraryUpdateItemRestriction().get() val isUpdateIntervalEnabled = LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in libraryPreferences.libraryUpdateItemRestriction().get()
private val leadDay = libraryPreferences.leadingAnimeExpectedDays().get() val leadDay = libraryPreferences.leadingAnimeExpectedDays().get()
private val followDay = libraryPreferences.followingAnimeExpectedDays().get() val followDay = libraryPreferences.followingAnimeExpectedDays().get()
private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
private val selectedEpisodeIds: HashSet<Long> = HashSet() private val selectedEpisodeIds: HashSet<Long> = HashSet()
@ -296,7 +295,7 @@ class AnimeScreenModel(
// Add to library // Add to library
// First, check if duplicate exists if callback is provided // First, check if duplicate exists if callback is provided
if (checkDuplicate) { if (checkDuplicate) {
val duplicate = getDuplicateLibraryAnime.await(anime.title) val duplicate = getDuplicateLibraryAnime.await(anime).getOrNull(0)
if (duplicate != null) { if (duplicate != null) {
updateSuccessState { it.copy(dialog = Dialog.DuplicateAnime(anime, duplicate)) } updateSuccessState { it.copy(dialog = Dialog.DuplicateAnime(anime, duplicate)) }
return@launchIO return@launchIO
@ -371,30 +370,23 @@ class AnimeScreenModel(
} }
} }
fun showSetAnimeIntervalDialog() { fun showSetAnimeFetchIntervalDialog() {
val anime = successState?.anime ?: return val anime = successState?.anime ?: return
updateSuccessState { updateSuccessState {
it.copy(dialog = Dialog.SetAnimeInterval(anime)) it.copy(dialog = Dialog.SetAnimeFetchInterval(anime))
} }
} }
// TODO: this should be in the state/composables fun setFetchInterval(anime: Anime, newInterval: Int) {
fun intervalDisplay(): Pair<Int, Int>? {
val anime = successState?.anime ?: return null
val effInterval = anime.calculateInterval
return 1.coerceAtLeast(effInterval.absoluteValue - leadDay) to (effInterval.absoluteValue + followDay)
}
fun setFetchRangeInterval(anime: Anime, newInterval: Int) {
val interval = when (newInterval) { val interval = when (newInterval) {
// reset interval 0 default to trigger recalculation // reset interval 0 default to trigger recalculation
// only reset if interval is custom, which is negative // only reset if interval is custom, which is negative
0 -> if (anime.calculateInterval < 0) 0 else anime.calculateInterval 0 -> if (anime.fetchInterval < 0) 0 else anime.fetchInterval
else -> -newInterval else -> -newInterval
} }
coroutineScope.launchIO { coroutineScope.launchIO {
updateAnime.awaitUpdateFetchInterval( updateAnime.awaitUpdateFetchInterval(
anime.copy(calculateInterval = interval), anime.copy(fetchInterval = interval),
successState?.episodes?.map { it.episode }.orEmpty(), successState?.episodes?.map { it.episode }.orEmpty(),
) )
val newAnime = animeRepository.getAnimeById(animeId) val newAnime = animeRepository.getAnimeById(animeId)
@ -1011,7 +1003,7 @@ class AnimeScreenModel(
data class ChangeCategory(val anime: Anime, val initialSelection: List<CheckboxState<Category>>) : Dialog data class ChangeCategory(val anime: Anime, val initialSelection: List<CheckboxState<Category>>) : Dialog
data class DeleteEpisodes(val episodes: List<Episode>) : Dialog data class DeleteEpisodes(val episodes: List<Episode>) : Dialog
data class DuplicateAnime(val anime: Anime, val duplicate: Anime) : Dialog data class DuplicateAnime(val anime: Anime, val duplicate: Anime) : Dialog
data class SetAnimeInterval(val anime: Anime) : Dialog data class SetAnimeFetchInterval(val anime: Anime) : Dialog
data class ShowQualities(val episode: Episode, val anime: Anime, val source: AnimeSource) : Dialog data class ShowQualities(val episode: Episode, val anime: Anime, val source: AnimeSource) : Dialog
data object ChangeAnimeSkipIntro : Dialog data object ChangeAnimeSkipIntro : Dialog
data object SettingsSheet : Dialog data object SettingsSheet : Dialog
@ -1113,3 +1105,10 @@ data class EpisodeItem(
) { ) {
val isDownloaded = downloadState == AnimeDownload.State.DOWNLOADED val isDownloaded = downloadState == AnimeDownload.State.DOWNLOADED
} }
@Immutable
data class FetchAnimeInterval(
val interval: Int,
val leadDays: Int,
val followDays: Int,
)

View file

@ -84,6 +84,13 @@ class MangaScreen(
val successState = state as MangaScreenModel.State.Success val successState = state as MangaScreenModel.State.Success
val isHttpSource = remember { successState.source is HttpSource } val isHttpSource = remember { successState.source is HttpSource }
val fetchInterval = remember(successState.manga.fetchInterval) {
FetchMangaInterval(
interval = successState.manga.fetchInterval,
leadDays = screenModel.leadDay,
followDays = screenModel.followDay,
)
}
LaunchedEffect(successState.manga, screenModel.source) { LaunchedEffect(successState.manga, screenModel.source) {
if (isHttpSource) { if (isHttpSource) {
@ -101,7 +108,7 @@ class MangaScreen(
state = successState, state = successState,
snackbarHostState = screenModel.snackbarHostState, snackbarHostState = screenModel.snackbarHostState,
dateFormat = screenModel.dateFormat, dateFormat = screenModel.dateFormat,
intervalDisplay = screenModel::intervalDisplay, fetchInterval = fetchInterval,
isTabletUi = isTabletUi(), isTabletUi = isTabletUi(),
chapterSwipeStartAction = screenModel.chapterSwipeStartAction, chapterSwipeStartAction = screenModel.chapterSwipeStartAction,
chapterSwipeEndAction = screenModel.chapterSwipeEndAction, chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
@ -124,7 +131,7 @@ class MangaScreen(
onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource }, onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() }, onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.manga.favorite }, onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.manga.favorite },
onEditIntervalClicked = screenModel::showSetMangaIntervalDialog.takeIf { screenModel.isIntervalEnabled && successState.manga.favorite }, onEditFetchIntervalClicked = screenModel::showSetMangaFetchIntervalDialog.takeIf { screenModel.isUpdateIntervalEnabled && successState.manga.favorite },
onMigrateClicked = { navigator.push(MigrateSearchScreen(successState.manga.id)) }.takeIf { successState.manga.favorite }, onMigrateClicked = { navigator.push(MigrateSearchScreen(successState.manga.id)) }.takeIf { successState.manga.favorite },
onMultiBookmarkClicked = screenModel::bookmarkChapters, onMultiBookmarkClicked = screenModel::bookmarkChapters,
onMultiMarkAsReadClicked = screenModel::markChaptersRead, onMultiMarkAsReadClicked = screenModel::markChaptersRead,
@ -217,11 +224,11 @@ class MangaScreen(
LoadingScreen(Modifier.systemBarsPadding()) LoadingScreen(Modifier.systemBarsPadding())
} }
} }
is MangaScreenModel.Dialog.SetMangaInterval -> { is MangaScreenModel.Dialog.SetMangaFetchInterval -> {
SetIntervalDialog( SetIntervalDialog(
interval = if (dialog.manga.calculateInterval < 0) -dialog.manga.calculateInterval else 0, interval = if (dialog.manga.fetchInterval < 0) -dialog.manga.fetchInterval else 0,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onValueChanged = { screenModel.setFetchRangeInterval(dialog.manga, it) }, onValueChanged = { screenModel.setFetchInterval(dialog.manga, it) },
) )
} }
} }

View file

@ -76,7 +76,6 @@ import tachiyomi.domain.track.manga.interactor.GetMangaTracks
import tachiyomi.source.local.entries.manga.isLocal import tachiyomi.source.local.entries.manga.isLocal
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import kotlin.math.absoluteValue
class MangaScreenModel( class MangaScreenModel(
val context: Context, val context: Context,
@ -131,9 +130,9 @@ class MangaScreenModel(
val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
val skipFiltered by readerPreferences.skipFiltered().asState(coroutineScope) val skipFiltered by readerPreferences.skipFiltered().asState(coroutineScope)
val isIntervalEnabled = LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in libraryPreferences.libraryUpdateItemRestriction().get() val isUpdateIntervalEnabled = LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in libraryPreferences.libraryUpdateItemRestriction().get()
private val leadDay = libraryPreferences.leadingMangaExpectedDays().get() val leadDay = libraryPreferences.leadingMangaExpectedDays().get()
private val followDay = libraryPreferences.followingMangaExpectedDays().get() val followDay = libraryPreferences.followingMangaExpectedDays().get()
private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
private val selectedChapterIds: HashSet<Long> = HashSet() private val selectedChapterIds: HashSet<Long> = HashSet()
@ -292,7 +291,7 @@ class MangaScreenModel(
// Add to library // Add to library
// First, check if duplicate exists if callback is provided // First, check if duplicate exists if callback is provided
if (checkDuplicate) { if (checkDuplicate) {
val duplicate = getDuplicateLibraryManga.await(manga.title) val duplicate = getDuplicateLibraryManga.await(manga).getOrNull(0)
if (duplicate != null) { if (duplicate != null) {
updateSuccessState { it.copy(dialog = Dialog.DuplicateManga(manga, duplicate)) } updateSuccessState { it.copy(dialog = Dialog.DuplicateManga(manga, duplicate)) }
@ -368,30 +367,23 @@ class MangaScreenModel(
} }
} }
fun showSetMangaIntervalDialog() { fun showSetMangaFetchIntervalDialog() {
val manga = successState?.manga ?: return val manga = successState?.manga ?: return
updateSuccessState { updateSuccessState {
it.copy(dialog = Dialog.SetMangaInterval(manga)) it.copy(dialog = Dialog.SetMangaFetchInterval(manga))
} }
} }
// TODO: this should be in the state/composables fun setFetchInterval(manga: Manga, newInterval: Int) {
fun intervalDisplay(): Pair<Int, Int>? {
val manga = successState?.manga ?: return null
val effInterval = manga.calculateInterval
return 1.coerceAtLeast(effInterval.absoluteValue - leadDay) to (effInterval.absoluteValue + followDay)
}
fun setFetchRangeInterval(manga: Manga, newInterval: Int) {
val interval = when (newInterval) { val interval = when (newInterval) {
// reset interval 0 default to trigger recalculation // reset interval 0 default to trigger recalculation
// only reset if interval is custom, which is negative // only reset if interval is custom, which is negative
0 -> if (manga.calculateInterval < 0) 0 else manga.calculateInterval 0 -> if (manga.fetchInterval < 0) 0 else manga.fetchInterval
else -> -newInterval else -> -newInterval
} }
coroutineScope.launchIO { coroutineScope.launchIO {
updateManga.awaitUpdateFetchInterval( updateManga.awaitUpdateFetchInterval(
manga.copy(calculateInterval = interval), manga.copy(fetchInterval = interval),
successState?.chapters?.map { it.chapter }.orEmpty(), successState?.chapters?.map { it.chapter }.orEmpty(),
) )
val newManga = mangaRepository.getMangaById(mangaId) val newManga = mangaRepository.getMangaById(mangaId)
@ -996,7 +988,7 @@ class MangaScreenModel(
data class ChangeCategory(val manga: Manga, val initialSelection: List<CheckboxState<Category>>) : Dialog data class ChangeCategory(val manga: Manga, val initialSelection: List<CheckboxState<Category>>) : Dialog
data class DeleteChapters(val chapters: List<Chapter>) : Dialog data class DeleteChapters(val chapters: List<Chapter>) : Dialog
data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog
data class SetMangaInterval(val manga: Manga) : Dialog data class SetMangaFetchInterval(val manga: Manga) : Dialog
data object SettingsSheet : Dialog data object SettingsSheet : Dialog
data object TrackSheet : Dialog data object TrackSheet : Dialog
data object FullCover : Dialog data object FullCover : Dialog
@ -1081,3 +1073,10 @@ data class ChapterItem(
) { ) {
val isDownloaded = downloadState == MangaDownload.State.DOWNLOADED val isDownloaded = downloadState == MangaDownload.State.DOWNLOADED
} }
@Immutable
data class FetchMangaInterval(
val interval: Int,
val leadDays: Int,
val followDays: Int,
)

View file

@ -85,7 +85,8 @@ class AnimeUpdatesScreenModel(
combine( combine(
getUpdates.subscribe(calendar).distinctUntilChanged(), getUpdates.subscribe(calendar).distinctUntilChanged(),
downloadCache.changes, downloadCache.changes,
) { updates, _ -> updates } downloadManager.queueState,
) { updates, _, _ -> updates }
.catch { .catch {
logcat(LogPriority.ERROR, it) logcat(LogPriority.ERROR, it)
_events.send(Event.InternalError) _events.send(Event.InternalError)

View file

@ -81,7 +81,8 @@ class MangaUpdatesScreenModel(
combine( combine(
getUpdates.subscribe(calendar).distinctUntilChanged(), getUpdates.subscribe(calendar).distinctUntilChanged(),
downloadCache.changes, downloadCache.changes,
) { updates, _ -> updates } downloadManager.queueState,
) { updates, _, _ -> updates }
.catch { .catch {
logcat(LogPriority.ERROR, it) logcat(LogPriority.ERROR, it)
_events.send(Event.InternalError) _events.send(Event.InternalError)

View file

@ -120,7 +120,7 @@ fun Date.toRelativeString(
val difference = now.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - this.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) val difference = now.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - this.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY)
val days = difference.floorDiv(MILLISECONDS_IN_DAY).toInt() val days = difference.floorDiv(MILLISECONDS_IN_DAY).toInt()
return when { return when {
difference < 0 -> context.getString(R.string.recently) difference < 0 -> dateFormat.format(this)
difference < MILLISECONDS_IN_DAY -> context.getString(R.string.relative_time_today) difference < MILLISECONDS_IN_DAY -> context.getString(R.string.relative_time_today)
difference < MILLISECONDS_IN_DAY.times(7) -> context.resources.getQuantityString( difference < MILLISECONDS_IN_DAY.times(7) -> context.resources.getQuantityString(
R.plurals.relative_time, R.plurals.relative_time,

View file

@ -1,8 +1,10 @@
import org.jetbrains.kotlin.cli.jvm.main
plugins { plugins {
id("com.android.library") id("com.android.library")
kotlin("android") kotlin("android")
kotlin("plugin.serialization") kotlin("plugin.serialization")
id("com.squareup.sqldelight") id("app.cash.sqldelight")
} }
android { android {
@ -13,17 +15,19 @@ android {
} }
sqldelight { sqldelight {
database("Database") { databases {
packageName = "tachiyomi.data" create("Database") {
dialect = "sqlite:3.24" packageName.set("tachiyomi.data")
schemaOutputDirectory = project.file("./src/main/sqldelight") dialect(libs.sqldelight.dialects.sql)
sourceFolders = listOf("sqldelight") schemaOutputDirectory.set(project.file("./src/main/sqldelight"))
srcDirs.from(project.file("./src/main/sqldelight"))
}
create("AnimeDatabase") {
packageName.set("tachiyomi.mi.data")
dialect(libs.sqldelight.dialects.sql)
schemaOutputDirectory.set(project.file("./src/main/sqldelightanime"))
srcDirs.from(project.file("./src/main/sqldelightanime"))
} }
database("AnimeDatabase") {
packageName = "tachiyomi.mi.data"
dialect = "sqlite:3.24"
schemaOutputDirectory = project.file("./src/main/sqldelightanime")
sourceFolders = listOf("sqldelightanime")
} }
} }
} }
@ -33,9 +37,7 @@ dependencies {
implementation(project(":domain")) implementation(project(":domain"))
implementation(project(":core")) implementation(project(":core"))
api(libs.sqldelight.android.driver) api(libs.bundles.sqldelight)
api(libs.sqldelight.coroutines)
api(libs.sqldelight.android.paging)
} }
tasks { tasks {

View file

@ -1,6 +1,6 @@
package tachiyomi.data package tachiyomi.data
import com.squareup.sqldelight.ColumnAdapter import app.cash.sqldelight.ColumnAdapter
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import java.util.Date import java.util.Date

View file

@ -12,7 +12,7 @@ val animeMapper: (Long, Long, String, String?, String?, String?, List<String>?,
favorite = favorite, favorite = favorite,
lastUpdate = lastUpdate ?: 0, lastUpdate = lastUpdate ?: 0,
nextUpdate = nextUpdate ?: 0, nextUpdate = nextUpdate ?: 0,
calculateInterval = calculateInterval.toInt(), fetchInterval = calculateInterval.toInt(),
dateAdded = dateAdded, dateAdded = dateAdded,
viewerFlags = viewerFlags, viewerFlags = viewerFlags,
episodeFlags = episodeFlags, episodeFlags = episodeFlags,
@ -32,7 +32,7 @@ val animeMapper: (Long, Long, String, String?, String?, String?, List<String>?,
) )
} }
val libraryAnime: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long, Long?, Long, Long, Long, Long, Long, Long, Long) -> LibraryAnime = val libraryAnime: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long, Long?, Long, Double, Long, Long, Long, Double, Long) -> LibraryAnime =
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, episodeFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, favoriteModifiedAt, totalCount, seenCount, latestUpload, episodeFetchedAt, lastSeen, bookmarkCount, category -> { id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, episodeFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, favoriteModifiedAt, totalCount, seenCount, latestUpload, episodeFetchedAt, lastSeen, bookmarkCount, category ->
LibraryAnime( LibraryAnime(
anime = animeMapper( anime = animeMapper(
@ -61,8 +61,8 @@ val libraryAnime: (Long, Long, String, String?, String?, String?, List<String>?,
), ),
category = category, category = category,
totalEpisodes = totalCount, totalEpisodes = totalCount,
seenCount = seenCount, seenCount = seenCount.toLong(),
bookmarkCount = bookmarkCount, bookmarkCount = bookmarkCount.toLong(),
latestUpload = latestUpload, latestUpload = latestUpload,
episodeFetchedAt = episodeFetchedAt, episodeFetchedAt = episodeFetchedAt,
lastSeen = lastSeen, lastSeen = lastSeen,

View file

@ -48,9 +48,9 @@ class AnimeRepositoryImpl(
return handler.subscribeToList { animesQueries.getFavoriteBySourceId(sourceId, animeMapper) } return handler.subscribeToList { animesQueries.getFavoriteBySourceId(sourceId, animeMapper) }
} }
override suspend fun getDuplicateLibraryAnime(title: String): Anime? { override suspend fun getDuplicateLibraryAnime(id: Long, title: String): List<Anime> {
return handler.awaitOneOrNull { return handler.awaitList {
animesQueries.getDuplicateLibraryAnime(title, animeMapper) animesQueries.getDuplicateLibraryAnime(title, id, animeMapper)
} }
} }
@ -74,7 +74,7 @@ class AnimeRepositoryImpl(
} }
override suspend fun insertAnime(anime: Anime): Long? { override suspend fun insertAnime(anime: Anime): Long? {
return handler.awaitOneOrNull(inTransaction = true) { return handler.awaitOneOrNullExecutable(inTransaction = true) {
animesQueries.insert( animesQueries.insert(
source = anime.source, source = anime.source,
url = anime.url, url = anime.url,
@ -88,7 +88,7 @@ class AnimeRepositoryImpl(
favorite = anime.favorite, favorite = anime.favorite,
lastUpdate = anime.lastUpdate, lastUpdate = anime.lastUpdate,
nextUpdate = anime.nextUpdate, nextUpdate = anime.nextUpdate,
calculateInterval = anime.calculateInterval.toLong(), calculateInterval = anime.fetchInterval.toLong(),
initialized = anime.initialized, initialized = anime.initialized,
viewerFlags = anime.viewerFlags, viewerFlags = anime.viewerFlags,
episodeFlags = anime.episodeFlags, episodeFlags = anime.episodeFlags,
@ -133,11 +133,11 @@ class AnimeRepositoryImpl(
title = value.title, title = value.title,
status = value.status, status = value.status,
thumbnailUrl = value.thumbnailUrl, thumbnailUrl = value.thumbnailUrl,
favorite = value.favorite?.toLong(), favorite = value.favorite,
lastUpdate = value.lastUpdate, lastUpdate = value.lastUpdate,
nextUpdate = value.nextUpdate, nextUpdate = value.nextUpdate,
calculateInterval = value.calculateInterval?.toLong(), calculateInterval = value.fetchInterval?.toLong(),
initialized = value.initialized?.toLong(), initialized = value.initialized,
viewer = value.viewerFlags, viewer = value.viewerFlags,
episodeFlags = value.episodeFlags, episodeFlags = value.episodeFlags,
coverLastModified = value.coverLastModified, coverLastModified = value.coverLastModified,

View file

@ -12,7 +12,7 @@ val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?,
favorite = favorite, favorite = favorite,
lastUpdate = lastUpdate ?: 0, lastUpdate = lastUpdate ?: 0,
nextUpdate = nextUpdate ?: 0, nextUpdate = nextUpdate ?: 0,
calculateInterval = calculateInterval.toInt(), fetchInterval = calculateInterval.toInt(),
dateAdded = dateAdded, dateAdded = dateAdded,
viewerFlags = viewerFlags, viewerFlags = viewerFlags,
chapterFlags = chapterFlags, chapterFlags = chapterFlags,
@ -32,7 +32,7 @@ val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?,
) )
} }
val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long, Long?, Long, Long, Long, Long, Long, Long, Long) -> LibraryManga = val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long, Long?, Long, Double, Long, Long, Long, Double, Long) -> LibraryManga =
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, favoriteModifiedAt, totalCount, readCount, latestUpload, chapterFetchedAt, lastRead, bookmarkCount, category -> { id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, favoriteModifiedAt, totalCount, readCount, latestUpload, chapterFetchedAt, lastRead, bookmarkCount, category ->
LibraryManga( LibraryManga(
manga = mangaMapper( manga = mangaMapper(
@ -61,8 +61,8 @@ val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?,
), ),
category = category, category = category,
totalChapters = totalCount, totalChapters = totalCount,
readCount = readCount, readCount = readCount.toLong(),
bookmarkCount = bookmarkCount, bookmarkCount = bookmarkCount.toLong(),
latestUpload = latestUpload, latestUpload = latestUpload,
chapterFetchedAt = chapterFetchedAt, chapterFetchedAt = chapterFetchedAt,
lastRead = lastRead, lastRead = lastRead,

View file

@ -48,9 +48,9 @@ class MangaRepositoryImpl(
return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, mangaMapper) } return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, mangaMapper) }
} }
override suspend fun getDuplicateLibraryManga(title: String): Manga? { override suspend fun getDuplicateLibraryManga(id: Long, title: String): List<Manga> {
return handler.awaitOneOrNull { return handler.awaitList {
mangasQueries.getDuplicateLibraryManga(title, mangaMapper) mangasQueries.getDuplicateLibraryManga(title, id, mangaMapper)
} }
} }
@ -74,7 +74,7 @@ class MangaRepositoryImpl(
} }
override suspend fun insertManga(manga: Manga): Long? { override suspend fun insertManga(manga: Manga): Long? {
return handler.awaitOneOrNull(inTransaction = true) { return handler.awaitOneOrNullExecutable(inTransaction = true) {
mangasQueries.insert( mangasQueries.insert(
source = manga.source, source = manga.source,
url = manga.url, url = manga.url,
@ -88,7 +88,7 @@ class MangaRepositoryImpl(
favorite = manga.favorite, favorite = manga.favorite,
lastUpdate = manga.lastUpdate, lastUpdate = manga.lastUpdate,
nextUpdate = manga.nextUpdate, nextUpdate = manga.nextUpdate,
calculateInterval = manga.calculateInterval.toLong(), calculateInterval = manga.fetchInterval.toLong(),
initialized = manga.initialized, initialized = manga.initialized,
viewerFlags = manga.viewerFlags, viewerFlags = manga.viewerFlags,
chapterFlags = manga.chapterFlags, chapterFlags = manga.chapterFlags,
@ -133,11 +133,11 @@ class MangaRepositoryImpl(
title = value.title, title = value.title,
status = value.status, status = value.status,
thumbnailUrl = value.thumbnailUrl, thumbnailUrl = value.thumbnailUrl,
favorite = value.favorite?.toLong(), favorite = value.favorite,
lastUpdate = value.lastUpdate, lastUpdate = value.lastUpdate,
nextUpdate = value.nextUpdate, nextUpdate = value.nextUpdate,
calculateInterval = value.calculateInterval?.toLong(), calculateInterval = value.fetchInterval?.toLong(),
initialized = value.initialized?.toLong(), initialized = value.initialized,
viewer = value.viewerFlags, viewer = value.viewerFlags,
chapterFlags = value.chapterFlags, chapterFlags = value.chapterFlags,
coverLastModified = value.coverLastModified, coverLastModified = value.coverLastModified,

View file

@ -1,12 +1,13 @@
package tachiyomi.data.handlers.anime package tachiyomi.data.handlers.anime
import androidx.paging.PagingSource import androidx.paging.PagingSource
import com.squareup.sqldelight.Query import app.cash.sqldelight.ExecutableQuery
import com.squareup.sqldelight.db.SqlDriver import app.cash.sqldelight.Query
import com.squareup.sqldelight.runtime.coroutines.asFlow import app.cash.sqldelight.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList import app.cash.sqldelight.coroutines.mapToList
import com.squareup.sqldelight.runtime.coroutines.mapToOne import app.cash.sqldelight.coroutines.mapToOne
import com.squareup.sqldelight.runtime.coroutines.mapToOneOrNull import app.cash.sqldelight.coroutines.mapToOneOrNull
import app.cash.sqldelight.db.SqlDriver
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -40,6 +41,13 @@ class AndroidAnimeDatabaseHandler(
return dispatch(inTransaction) { block(db).executeAsOne() } return dispatch(inTransaction) { block(db).executeAsOne() }
} }
override suspend fun <T : Any> awaitOneExecutable(
inTransaction: Boolean,
block: suspend AnimeDatabase.() -> ExecutableQuery<T>,
): T {
return dispatch(inTransaction) { block(db).executeAsOne() }
}
override suspend fun <T : Any> awaitOneOrNull( override suspend fun <T : Any> awaitOneOrNull(
inTransaction: Boolean, inTransaction: Boolean,
block: suspend AnimeDatabase.() -> Query<T>, block: suspend AnimeDatabase.() -> Query<T>,
@ -47,6 +55,13 @@ class AndroidAnimeDatabaseHandler(
return dispatch(inTransaction) { block(db).executeAsOneOrNull() } return dispatch(inTransaction) { block(db).executeAsOneOrNull() }
} }
override suspend fun <T : Any> awaitOneOrNullExecutable(
inTransaction: Boolean,
block: suspend AnimeDatabase.() -> ExecutableQuery<T>,
): T? {
return dispatch(inTransaction) { block(db).executeAsOneOrNull() }
}
override fun <T : Any> subscribeToList(block: AnimeDatabase.() -> Query<T>): Flow<List<T>> { override fun <T : Any> subscribeToList(block: AnimeDatabase.() -> Query<T>): Flow<List<T>> {
return block(db).asFlow().mapToList(queryDispatcher) return block(db).asFlow().mapToList(queryDispatcher)
} }

View file

@ -1,7 +1,8 @@
package tachiyomi.data.handlers.anime package tachiyomi.data.handlers.anime
import androidx.paging.PagingSource import androidx.paging.PagingSource
import com.squareup.sqldelight.Query import app.cash.sqldelight.ExecutableQuery
import app.cash.sqldelight.Query
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import tachiyomi.mi.data.AnimeDatabase import tachiyomi.mi.data.AnimeDatabase
@ -19,11 +20,21 @@ interface AnimeDatabaseHandler {
block: suspend AnimeDatabase.() -> Query<T>, block: suspend AnimeDatabase.() -> Query<T>,
): T ): T
suspend fun <T : Any> awaitOneExecutable(
inTransaction: Boolean = false,
block: suspend AnimeDatabase.() -> ExecutableQuery<T>,
): T
suspend fun <T : Any> awaitOneOrNull( suspend fun <T : Any> awaitOneOrNull(
inTransaction: Boolean = false, inTransaction: Boolean = false,
block: suspend AnimeDatabase.() -> Query<T>, block: suspend AnimeDatabase.() -> Query<T>,
): T? ): T?
suspend fun <T : Any> awaitOneOrNullExecutable(
inTransaction: Boolean = false,
block: suspend AnimeDatabase.() -> ExecutableQuery<T>,
): T?
fun <T : Any> subscribeToList(block: AnimeDatabase.() -> Query<T>): Flow<List<T>> fun <T : Any> subscribeToList(block: AnimeDatabase.() -> Query<T>): Flow<List<T>>
fun <T : Any> subscribeToOne(block: AnimeDatabase.() -> Query<T>): Flow<T> fun <T : Any> subscribeToOne(block: AnimeDatabase.() -> Query<T>): Flow<T>

View file

@ -2,7 +2,7 @@ package tachiyomi.data.handlers.anime
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.squareup.sqldelight.Query import app.cash.sqldelight.Query
import tachiyomi.mi.data.AnimeDatabase import tachiyomi.mi.data.AnimeDatabase
import kotlin.properties.Delegates import kotlin.properties.Delegates

View file

@ -1,12 +1,13 @@
package tachiyomi.data.handlers.manga package tachiyomi.data.handlers.manga
import androidx.paging.PagingSource import androidx.paging.PagingSource
import com.squareup.sqldelight.Query import app.cash.sqldelight.ExecutableQuery
import com.squareup.sqldelight.db.SqlDriver import app.cash.sqldelight.Query
import com.squareup.sqldelight.runtime.coroutines.asFlow import app.cash.sqldelight.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList import app.cash.sqldelight.coroutines.mapToList
import com.squareup.sqldelight.runtime.coroutines.mapToOne import app.cash.sqldelight.coroutines.mapToOne
import com.squareup.sqldelight.runtime.coroutines.mapToOneOrNull import app.cash.sqldelight.coroutines.mapToOneOrNull
import app.cash.sqldelight.db.SqlDriver
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -40,6 +41,13 @@ class AndroidMangaDatabaseHandler(
return dispatch(inTransaction) { block(db).executeAsOne() } return dispatch(inTransaction) { block(db).executeAsOne() }
} }
override suspend fun <T : Any> awaitOneExecutable(
inTransaction: Boolean,
block: suspend Database.() -> ExecutableQuery<T>,
): T {
return dispatch(inTransaction) { block(db).executeAsOne() }
}
override suspend fun <T : Any> awaitOneOrNull( override suspend fun <T : Any> awaitOneOrNull(
inTransaction: Boolean, inTransaction: Boolean,
block: suspend Database.() -> Query<T>, block: suspend Database.() -> Query<T>,
@ -47,6 +55,13 @@ class AndroidMangaDatabaseHandler(
return dispatch(inTransaction) { block(db).executeAsOneOrNull() } return dispatch(inTransaction) { block(db).executeAsOneOrNull() }
} }
override suspend fun <T : Any> awaitOneOrNullExecutable(
inTransaction: Boolean,
block: suspend Database.() -> ExecutableQuery<T>,
): T? {
return dispatch(inTransaction) { block(db).executeAsOneOrNull() }
}
override fun <T : Any> subscribeToList(block: Database.() -> Query<T>): Flow<List<T>> { override fun <T : Any> subscribeToList(block: Database.() -> Query<T>): Flow<List<T>> {
return block(db).asFlow().mapToList(queryDispatcher) return block(db).asFlow().mapToList(queryDispatcher)
} }

View file

@ -1,7 +1,8 @@
package tachiyomi.data.handlers.manga package tachiyomi.data.handlers.manga
import androidx.paging.PagingSource import androidx.paging.PagingSource
import com.squareup.sqldelight.Query import app.cash.sqldelight.ExecutableQuery
import app.cash.sqldelight.Query
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import tachiyomi.data.Database import tachiyomi.data.Database
@ -19,11 +20,21 @@ interface MangaDatabaseHandler {
block: suspend Database.() -> Query<T>, block: suspend Database.() -> Query<T>,
): T ): T
suspend fun <T : Any> awaitOneExecutable(
inTransaction: Boolean = false,
block: suspend Database.() -> ExecutableQuery<T>,
): T
suspend fun <T : Any> awaitOneOrNull( suspend fun <T : Any> awaitOneOrNull(
inTransaction: Boolean = false, inTransaction: Boolean = false,
block: suspend Database.() -> Query<T>, block: suspend Database.() -> Query<T>,
): T? ): T?
suspend fun <T : Any> awaitOneOrNullExecutable(
inTransaction: Boolean = false,
block: suspend Database.() -> ExecutableQuery<T>,
): T?
fun <T : Any> subscribeToList(block: Database.() -> Query<T>): Flow<List<T>> fun <T : Any> subscribeToList(block: Database.() -> Query<T>): Flow<List<T>>
fun <T : Any> subscribeToOne(block: Database.() -> Query<T>): Flow<T> fun <T : Any> subscribeToOne(block: Database.() -> Query<T>): Flow<T>

View file

@ -2,7 +2,7 @@ package tachiyomi.data.handlers.manga
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.squareup.sqldelight.Query import app.cash.sqldelight.Query
import tachiyomi.data.Database import tachiyomi.data.Database
import kotlin.properties.Delegates import kotlin.properties.Delegates

View file

@ -2,7 +2,6 @@ package tachiyomi.data.items.chapter
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.util.lang.toLong
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.data.handlers.manga.MangaDatabaseHandler import tachiyomi.data.handlers.manga.MangaDatabaseHandler
import tachiyomi.domain.items.chapter.model.Chapter import tachiyomi.domain.items.chapter.model.Chapter
@ -56,8 +55,8 @@ class ChapterRepositoryImpl(
url = chapterUpdate.url, url = chapterUpdate.url,
name = chapterUpdate.name, name = chapterUpdate.name,
scanlator = chapterUpdate.scanlator, scanlator = chapterUpdate.scanlator,
read = chapterUpdate.read?.toLong(), read = chapterUpdate.read,
bookmark = chapterUpdate.bookmark?.toLong(), bookmark = chapterUpdate.bookmark,
lastPageRead = chapterUpdate.lastPageRead, lastPageRead = chapterUpdate.lastPageRead,
chapterNumber = chapterUpdate.chapterNumber?.toDouble(), chapterNumber = chapterUpdate.chapterNumber?.toDouble(),
sourceOrder = chapterUpdate.sourceOrder, sourceOrder = chapterUpdate.sourceOrder,

View file

@ -2,7 +2,6 @@ package tachiyomi.data.items.episode
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.util.lang.toLong
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.data.handlers.anime.AnimeDatabaseHandler import tachiyomi.data.handlers.anime.AnimeDatabaseHandler
import tachiyomi.domain.items.episode.model.Episode import tachiyomi.domain.items.episode.model.Episode
@ -57,8 +56,8 @@ class EpisodeRepositoryImpl(
url = episodeUpdate.url, url = episodeUpdate.url,
name = episodeUpdate.name, name = episodeUpdate.name,
scanlator = episodeUpdate.scanlator, scanlator = episodeUpdate.scanlator,
seen = episodeUpdate.seen?.toLong(), seen = episodeUpdate.seen,
bookmark = episodeUpdate.bookmark?.toLong(), bookmark = episodeUpdate.bookmark,
lastSecondSeen = episodeUpdate.lastSecondSeen, lastSecondSeen = episodeUpdate.lastSecondSeen,
totalSeconds = episodeUpdate.totalSeconds, totalSeconds = episodeUpdate.totalSeconds,
episodeNumber = episodeUpdate.episodeNumber?.toDouble(), episodeNumber = episodeUpdate.episodeNumber?.toDouble(),

View file

@ -1,3 +1,6 @@
import kotlin.Boolean;
import kotlin.Float;
CREATE TABLE chapters( CREATE TABLE chapters(
_id INTEGER NOT NULL PRIMARY KEY, _id INTEGER NOT NULL PRIMARY KEY,
manga_id INTEGER NOT NULL, manga_id INTEGER NOT NULL,
@ -9,9 +12,9 @@ CREATE TABLE chapters(
last_page_read INTEGER NOT NULL, last_page_read INTEGER NOT NULL,
chapter_number REAL AS Float NOT NULL, chapter_number REAL AS Float NOT NULL,
source_order INTEGER NOT NULL, source_order INTEGER NOT NULL,
date_fetch INTEGER AS Long NOT NULL, date_fetch INTEGER NOT NULL,
date_upload INTEGER AS Long NOT NULL, date_upload INTEGER NOT NULL,
last_modified_at INTEGER AS Long NOT NULL DEFAULT 0, last_modified_at INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(manga_id) REFERENCES mangas (_id) FOREIGN KEY(manga_id) REFERENCES mangas (_id)
ON DELETE CASCADE ON DELETE CASCADE
); );

View file

@ -1,3 +1,5 @@
import kotlin.Float;
CREATE TABLE manga_sync( CREATE TABLE manga_sync(
_id INTEGER NOT NULL PRIMARY KEY, _id INTEGER NOT NULL PRIMARY KEY,
manga_id INTEGER NOT NULL, manga_id INTEGER NOT NULL,
@ -10,8 +12,8 @@ CREATE TABLE manga_sync(
status INTEGER NOT NULL, status INTEGER NOT NULL,
score REAL AS Float NOT NULL, score REAL AS Float NOT NULL,
remote_url TEXT NOT NULL, remote_url TEXT NOT NULL,
start_date INTEGER AS Long NOT NULL, start_date INTEGER NOT NULL,
finish_date INTEGER AS Long NOT NULL, finish_date INTEGER NOT NULL,
UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE, UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE,
FOREIGN KEY(manga_id) REFERENCES mangas (_id) FOREIGN KEY(manga_id) REFERENCES mangas (_id)
ON DELETE CASCADE ON DELETE CASCADE

View file

@ -1,6 +1,7 @@
import eu.kanade.tachiyomi.source.model.UpdateStrategy; import eu.kanade.tachiyomi.source.model.UpdateStrategy;
import java.lang.String;
import kotlin.collections.List; import kotlin.collections.List;
import kotlin.Boolean;
import kotlin.String;
CREATE TABLE mangas( CREATE TABLE mangas(
_id INTEGER NOT NULL PRIMARY KEY, _id INTEGER NOT NULL PRIMARY KEY,
@ -14,17 +15,17 @@ CREATE TABLE mangas(
status INTEGER NOT NULL, status INTEGER NOT NULL,
thumbnail_url TEXT, thumbnail_url TEXT,
favorite INTEGER AS Boolean NOT NULL, favorite INTEGER AS Boolean NOT NULL,
last_update INTEGER AS Long, last_update INTEGER,
next_update INTEGER AS Long, next_update INTEGER,
initialized INTEGER AS Boolean NOT NULL, initialized INTEGER AS Boolean NOT NULL,
viewer INTEGER NOT NULL, viewer INTEGER NOT NULL,
chapter_flags INTEGER NOT NULL, chapter_flags INTEGER NOT NULL,
cover_last_modified INTEGER AS Long NOT NULL, cover_last_modified INTEGER NOT NULL,
date_added INTEGER AS Long NOT NULL, date_added INTEGER NOT NULL,
update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0, update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0,
calculate_interval INTEGER DEFAULT 0 NOT NULL, calculate_interval INTEGER DEFAULT 0 NOT NULL,
last_modified_at INTEGER AS Long NOT NULL DEFAULT 0, last_modified_at INTEGER NOT NULL DEFAULT 0,
favorite_modified_at INTEGER AS Long favorite_modified_at INTEGER
); );
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1; CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
@ -92,7 +93,7 @@ SELECT *
FROM mangas FROM mangas
WHERE favorite = 1 WHERE favorite = 1
AND LOWER(title) = :title AND LOWER(title) = :title
LIMIT 1; AND _id != :id;
resetViewerFlags: resetViewerFlags:
UPDATE mangas UPDATE mangas

View file

@ -2,7 +2,7 @@ CREATE TABLE mangas_categories(
_id INTEGER NOT NULL PRIMARY KEY, _id INTEGER NOT NULL PRIMARY KEY,
manga_id INTEGER NOT NULL, manga_id INTEGER NOT NULL,
category_id INTEGER NOT NULL, category_id INTEGER NOT NULL,
last_modified_at INTEGER AS Long NOT NULL DEFAULT 0, last_modified_at INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(category_id) REFERENCES categories (_id) FOREIGN KEY(category_id) REFERENCES categories (_id)
ON DELETE CASCADE, ON DELETE CASCADE,
FOREIGN KEY(manga_id) REFERENCES mangas (_id) FOREIGN KEY(manga_id) REFERENCES mangas (_id)

View file

@ -1,3 +1,5 @@
import kotlin.Float;
CREATE TABLE anime_sync( CREATE TABLE anime_sync(
_id INTEGER NOT NULL PRIMARY KEY, _id INTEGER NOT NULL PRIMARY KEY,
anime_id INTEGER NOT NULL, anime_id INTEGER NOT NULL,
@ -10,8 +12,8 @@ CREATE TABLE anime_sync(
status INTEGER NOT NULL, status INTEGER NOT NULL,
score REAL AS Float NOT NULL, score REAL AS Float NOT NULL,
remote_url TEXT NOT NULL, remote_url TEXT NOT NULL,
start_date INTEGER AS Long NOT NULL, start_date INTEGER NOT NULL,
finish_date INTEGER AS Long NOT NULL, finish_date INTEGER NOT NULL,
UNIQUE (anime_id, sync_id) ON CONFLICT REPLACE, UNIQUE (anime_id, sync_id) ON CONFLICT REPLACE,
FOREIGN KEY(anime_id) REFERENCES animes (_id) FOREIGN KEY(anime_id) REFERENCES animes (_id)
ON DELETE CASCADE ON DELETE CASCADE

View file

@ -1,6 +1,7 @@
import eu.kanade.tachiyomi.source.model.UpdateStrategy; import eu.kanade.tachiyomi.source.model.UpdateStrategy;
import java.lang.String;
import kotlin.collections.List; import kotlin.collections.List;
import kotlin.Boolean;
import kotlin.String;
CREATE TABLE animes( CREATE TABLE animes(
_id INTEGER NOT NULL PRIMARY KEY, _id INTEGER NOT NULL PRIMARY KEY,
@ -14,17 +15,17 @@ CREATE TABLE animes(
status INTEGER NOT NULL, status INTEGER NOT NULL,
thumbnail_url TEXT, thumbnail_url TEXT,
favorite INTEGER AS Boolean NOT NULL, favorite INTEGER AS Boolean NOT NULL,
last_update INTEGER AS Long, last_update INTEGER,
next_update INTEGER AS Long, next_update INTEGER,
initialized INTEGER AS Boolean NOT NULL, initialized INTEGER AS Boolean NOT NULL,
viewer INTEGER NOT NULL, viewer INTEGER NOT NULL,
episode_flags INTEGER NOT NULL, episode_flags INTEGER NOT NULL,
cover_last_modified INTEGER AS Long NOT NULL, cover_last_modified INTEGER NOT NULL,
date_added INTEGER AS Long NOT NULL, date_added INTEGER NOT NULL,
update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0, update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0,
calculate_interval INTEGER DEFAULT 0 NOT NULL, calculate_interval INTEGER DEFAULT 0 NOT NULL,
last_modified_at INTEGER AS Long NOT NULL DEFAULT 0, last_modified_at INTEGER NOT NULL DEFAULT 0,
favorite_modified_at INTEGER AS Long favorite_modified_at INTEGER
); );
CREATE INDEX animelib_favorite_index ON animes(favorite) WHERE favorite = 1; CREATE INDEX animelib_favorite_index ON animes(favorite) WHERE favorite = 1;
@ -92,7 +93,7 @@ SELECT *
FROM animes FROM animes
WHERE favorite = 1 WHERE favorite = 1
AND LOWER(title) = :title AND LOWER(title) = :title
LIMIT 1; AND _id != :id;
resetViewerFlags: resetViewerFlags:
UPDATE animes UPDATE animes

View file

@ -2,7 +2,7 @@ CREATE TABLE animes_categories(
_id INTEGER NOT NULL PRIMARY KEY, _id INTEGER NOT NULL PRIMARY KEY,
anime_id INTEGER NOT NULL, anime_id INTEGER NOT NULL,
category_id INTEGER NOT NULL, category_id INTEGER NOT NULL,
last_modified_at INTEGER AS Long NOT NULL DEFAULT 0, last_modified_at INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(category_id) REFERENCES categories (_id) FOREIGN KEY(category_id) REFERENCES categories (_id)
ON DELETE CASCADE, ON DELETE CASCADE,
FOREIGN KEY(anime_id) REFERENCES animes (_id) FOREIGN KEY(anime_id) REFERENCES animes (_id)

View file

@ -1,3 +1,6 @@
import kotlin.Boolean;
import kotlin.Float;
CREATE TABLE episodes( CREATE TABLE episodes(
_id INTEGER NOT NULL PRIMARY KEY, _id INTEGER NOT NULL PRIMARY KEY,
anime_id INTEGER NOT NULL, anime_id INTEGER NOT NULL,
@ -10,9 +13,9 @@ CREATE TABLE episodes(
total_seconds INTEGER NOT NULL, total_seconds INTEGER NOT NULL,
episode_number REAL AS Float NOT NULL, episode_number REAL AS Float NOT NULL,
source_order INTEGER NOT NULL, source_order INTEGER NOT NULL,
date_fetch INTEGER AS Long NOT NULL, date_fetch INTEGER NOT NULL,
date_upload INTEGER AS Long NOT NULL, date_upload INTEGER NOT NULL,
last_modified_at INTEGER AS Long NOT NULL DEFAULT 0, last_modified_at INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(anime_id) REFERENCES animes (_id) FOREIGN KEY(anime_id) REFERENCES animes (_id)
ON DELETE CASCADE ON DELETE CASCADE
); );
@ -54,7 +57,7 @@ WHERE url = :episodeUrl;
getEpisodeByUrlAndAnimeId: getEpisodeByUrlAndAnimeId:
SELECT * SELECT *
FROM episodes FROM episodes
WHERE url = :chapterUrl WHERE url = :episodeUrl
AND anime_id = :animeId; AND anime_id = :animeId;
removeEpisodesWithIds: removeEpisodesWithIds:

View file

@ -30,7 +30,7 @@ SELECT
episodes._id AS episodeId, episodes._id AS episodeId,
animes.title, animes.title,
animes.thumbnail_url AS thumbnailUrl, animes.thumbnail_url AS thumbnailUrl,
episodes.chapter_number AS episodeNumber, episodes.episode_number AS episodeNumber,
animehistory.last_seen AS seenAt, animehistory.last_seen AS seenAt,
max_last_seen.last_seen AS maxSeenAt, max_last_seen.last_seen AS maxSeenAt,
max_last_seen.episode_id AS maxSeenAtEpisodeId max_last_seen.episode_id AS maxSeenAtEpisodeId
@ -47,4 +47,4 @@ JOIN (
) AS max_last_seen ) AS max_last_seen
ON episodes.anime_id = max_last_seen.anime_id; ON episodes.anime_id = max_last_seen.anime_id;
CREATE INDEX animehistory_history_chapter_id_index ON animehistory(episode_id); CREATE INDEX animehistory_history_episode_id_index ON animehistory(episode_id);

View file

@ -9,8 +9,8 @@ UPDATE animes_categories SET last_modified_at = strftime('%s', 'now');
UPDATE episodes SET last_modified_at = strftime('%s', 'now'); UPDATE episodes SET last_modified_at = strftime('%s', 'now');
-- Create triggers -- Create triggers
DROP TRIGGER IF EXISTS update_last_modified_at_mangas; DROP TRIGGER IF EXISTS update_last_modified_at_animes;
CREATE TRIGGER update_last_modified_at_mangas CREATE TRIGGER update_last_modified_at_animes
AFTER UPDATE ON animes AFTER UPDATE ON animes
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
@ -19,8 +19,8 @@ BEGIN
WHERE _id = new._id; WHERE _id = new._id;
END; END;
DROP TRIGGER IF EXISTS update_favorite_modified_at_mangas; DROP TRIGGER IF EXISTS update_favorite_modified_at_animes;
CREATE TRIGGER update_last_favorited_at_mangas CREATE TRIGGER update_last_favorited_at_animes
AFTER UPDATE OF favorite ON animes AFTER UPDATE OF favorite ON animes
BEGIN BEGIN
UPDATE animes UPDATE animes
@ -28,8 +28,8 @@ BEGIN
WHERE _id = new._id; WHERE _id = new._id;
END; END;
DROP TRIGGER IF EXISTS update_last_modified_at_chapters; DROP TRIGGER IF EXISTS update_last_modified_at_episodes;
CREATE TRIGGER update_last_modified_at_chapters CREATE TRIGGER update_last_modified_at_episodes
AFTER UPDATE ON episodes AFTER UPDATE ON episodes
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
@ -38,8 +38,8 @@ BEGIN
WHERE _id = new._id; WHERE _id = new._id;
END; END;
DROP TRIGGER IF EXISTS update_last_modified_at_mangas_categories; DROP TRIGGER IF EXISTS update_last_modified_at_animes_categories;
CREATE TRIGGER update_last_modified_at_mangas_categories CREATE TRIGGER update_last_modified_at_animes_categories
AFTER UPDATE ON animes_categories AFTER UPDATE ON animes_categories
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN

View file

@ -7,7 +7,7 @@ class GetDuplicateLibraryAnime(
private val animeRepository: AnimeRepository, private val animeRepository: AnimeRepository,
) { ) {
suspend fun await(title: String): Anime? { suspend fun await(anime: Anime): List<Anime> {
return animeRepository.getDuplicateLibraryAnime(title.lowercase()) return animeRepository.getDuplicateLibraryAnime(anime.id, anime.title.lowercase())
} }
} }

View file

@ -13,32 +13,32 @@ import kotlin.math.absoluteValue
const val MAX_GRACE_PERIOD = 28 const val MAX_GRACE_PERIOD = 28
class SetAnimeUpdateInterval( class SetAnimeFetchInterval(
private val libraryPreferences: LibraryPreferences = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(),
) { ) {
fun updateInterval( fun update(
anime: Anime, anime: Anime,
episodes: List<Episode>, episodes: List<Episode>,
zonedDateTime: ZonedDateTime, zonedDateTime: ZonedDateTime,
fetchRange: Pair<Long, Long>, fetchRange: Pair<Long, Long>,
): AnimeUpdate? { ): AnimeUpdate? {
val currentFetchRange = if (fetchRange.first == 0L && fetchRange.second == 0L) { val currentInterval = if (fetchRange.first == 0L && fetchRange.second == 0L) {
getCurrentFetchRange(ZonedDateTime.now()) getCurrent(ZonedDateTime.now())
} else { } else {
fetchRange fetchRange
} }
val interval = anime.calculateInterval.takeIf { it < 0 } ?: calculateInterval(episodes, zonedDateTime) val interval = anime.fetchInterval.takeIf { it < 0 } ?: calculateInterval(episodes, zonedDateTime)
val nextUpdate = calculateNextUpdate(anime, interval, zonedDateTime, currentFetchRange) val nextUpdate = calculateNextUpdate(anime, interval, zonedDateTime, currentInterval)
return if (anime.nextUpdate == nextUpdate && anime.calculateInterval == interval) { return if (anime.nextUpdate == nextUpdate && anime.fetchInterval == interval) {
null null
} else { } else {
AnimeUpdate(id = anime.id, nextUpdate = nextUpdate, calculateInterval = interval) AnimeUpdate(id = anime.id, nextUpdate = nextUpdate, fetchInterval = interval)
} }
} }
fun getCurrentFetchRange(timeToCal: ZonedDateTime): Pair<Long, Long> { fun getCurrent(timeToCal: ZonedDateTime): Pair<Long, Long> {
// lead range and the following range depend on if updateOnlyExpectedPeriod set. // lead range and the following range depend on if updateOnlyExpectedPeriod set.
var followRange = 0 var followRange = 0
var leadRange = 0 var leadRange = 0
@ -103,7 +103,7 @@ class SetAnimeUpdateInterval(
): Long { ): Long {
return if ( return if (
anime.nextUpdate !in fetchRange.first.rangeTo(fetchRange.second + 1) || anime.nextUpdate !in fetchRange.first.rangeTo(fetchRange.second + 1) ||
anime.calculateInterval == 0 anime.fetchInterval == 0
) { ) {
val latestDate = ZonedDateTime.ofInstant(Instant.ofEpochMilli(anime.lastUpdate), zonedDateTime.zone).toLocalDate().atStartOfDay() val latestDate = ZonedDateTime.ofInstant(Instant.ofEpochMilli(anime.lastUpdate), zonedDateTime.zone).toLocalDate().atStartOfDay()
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, zonedDateTime).toInt() val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, zonedDateTime).toInt()

View file

@ -11,7 +11,7 @@ data class Anime(
val favorite: Boolean, val favorite: Boolean,
val lastUpdate: Long, val lastUpdate: Long,
val nextUpdate: Long, val nextUpdate: Long,
val calculateInterval: Int, val fetchInterval: Int,
val dateAdded: Long, val dateAdded: Long,
val viewerFlags: Long, val viewerFlags: Long,
val episodeFlags: Long, val episodeFlags: Long,
@ -118,7 +118,7 @@ data class Anime(
favorite = false, favorite = false,
lastUpdate = 0L, lastUpdate = 0L,
nextUpdate = 0L, nextUpdate = 0L,
calculateInterval = 0, fetchInterval = 0,
dateAdded = 0L, dateAdded = 0L,
viewerFlags = 0L, viewerFlags = 0L,
episodeFlags = 0L, episodeFlags = 0L,

View file

@ -8,7 +8,7 @@ data class AnimeUpdate(
val favorite: Boolean? = null, val favorite: Boolean? = null,
val lastUpdate: Long? = null, val lastUpdate: Long? = null,
val nextUpdate: Long? = null, val nextUpdate: Long? = null,
val calculateInterval: Int? = null, val fetchInterval: Int? = null,
val dateAdded: Long? = null, val dateAdded: Long? = null,
val viewerFlags: Long? = null, val viewerFlags: Long? = null,
val episodeFlags: Long? = null, val episodeFlags: Long? = null,
@ -32,7 +32,7 @@ fun Anime.toAnimeUpdate(): AnimeUpdate {
favorite = favorite, favorite = favorite,
lastUpdate = lastUpdate, lastUpdate = lastUpdate,
nextUpdate = nextUpdate, nextUpdate = nextUpdate,
calculateInterval = calculateInterval, fetchInterval = fetchInterval,
dateAdded = dateAdded, dateAdded = dateAdded,
viewerFlags = viewerFlags, viewerFlags = viewerFlags,
episodeFlags = episodeFlags, episodeFlags = episodeFlags,

View file

@ -23,7 +23,7 @@ interface AnimeRepository {
fun getAnimeFavoritesBySourceId(sourceId: Long): Flow<List<Anime>> fun getAnimeFavoritesBySourceId(sourceId: Long): Flow<List<Anime>>
suspend fun getDuplicateLibraryAnime(title: String): Anime? suspend fun getDuplicateLibraryAnime(id: Long, title: String): List<Anime>
suspend fun resetAnimeViewerFlags(): Boolean suspend fun resetAnimeViewerFlags(): Boolean

View file

@ -7,7 +7,7 @@ class GetDuplicateLibraryManga(
private val mangaRepository: MangaRepository, private val mangaRepository: MangaRepository,
) { ) {
suspend fun await(title: String): Manga? { suspend fun await(manga: Manga): List<Manga> {
return mangaRepository.getDuplicateLibraryManga(title.lowercase()) return mangaRepository.getDuplicateLibraryManga(manga.id, manga.title.lowercase())
} }
} }

View file

@ -13,32 +13,32 @@ import kotlin.math.absoluteValue
const val MAX_GRACE_PERIOD = 28 const val MAX_GRACE_PERIOD = 28
class SetMangaUpdateInterval( class SetMangaFetchInterval(
private val libraryPreferences: LibraryPreferences = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(),
) { ) {
fun updateInterval( fun update(
manga: Manga, manga: Manga,
chapters: List<Chapter>, chapters: List<Chapter>,
zonedDateTime: ZonedDateTime, zonedDateTime: ZonedDateTime,
fetchRange: Pair<Long, Long>, fetchRange: Pair<Long, Long>,
): MangaUpdate? { ): MangaUpdate? {
val currentFetchRange = if (fetchRange.first == 0L && fetchRange.second == 0L) { val currentInterval = if (fetchRange.first == 0L && fetchRange.second == 0L) {
getCurrentFetchRange(ZonedDateTime.now()) getCurrent(ZonedDateTime.now())
} else { } else {
fetchRange fetchRange
} }
val interval = manga.calculateInterval.takeIf { it < 0 } ?: calculateInterval(chapters, zonedDateTime) val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(chapters, zonedDateTime)
val nextUpdate = calculateNextUpdate(manga, interval, zonedDateTime, currentFetchRange) val nextUpdate = calculateNextUpdate(manga, interval, zonedDateTime, currentInterval)
return if (manga.nextUpdate == nextUpdate && manga.calculateInterval == interval) { return if (manga.nextUpdate == nextUpdate && manga.fetchInterval == interval) {
null null
} else { } else {
MangaUpdate(id = manga.id, nextUpdate = nextUpdate, calculateInterval = interval) MangaUpdate(id = manga.id, nextUpdate = nextUpdate, fetchInterval = interval)
} }
} }
fun getCurrentFetchRange(timeToCal: ZonedDateTime): Pair<Long, Long> { fun getCurrent(timeToCal: ZonedDateTime): Pair<Long, Long> {
// lead range and the following range depend on if updateOnlyExpectedPeriod set. // lead range and the following range depend on if updateOnlyExpectedPeriod set.
var followRange = 0 var followRange = 0
var leadRange = 0 var leadRange = 0
@ -103,7 +103,7 @@ class SetMangaUpdateInterval(
): Long { ): Long {
return if ( return if (
manga.nextUpdate !in fetchRange.first.rangeTo(fetchRange.second + 1) || manga.nextUpdate !in fetchRange.first.rangeTo(fetchRange.second + 1) ||
manga.calculateInterval == 0 manga.fetchInterval == 0
) { ) {
val latestDate = ZonedDateTime.ofInstant(Instant.ofEpochMilli(manga.lastUpdate), zonedDateTime.zone).toLocalDate().atStartOfDay() val latestDate = ZonedDateTime.ofInstant(Instant.ofEpochMilli(manga.lastUpdate), zonedDateTime.zone).toLocalDate().atStartOfDay()
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, zonedDateTime).toInt() val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, zonedDateTime).toInt()

View file

@ -10,7 +10,7 @@ data class Manga(
val favorite: Boolean, val favorite: Boolean,
val lastUpdate: Long, val lastUpdate: Long,
val nextUpdate: Long, val nextUpdate: Long,
val calculateInterval: Int, val fetchInterval: Int,
val dateAdded: Long, val dateAdded: Long,
val viewerFlags: Long, val viewerFlags: Long,
val chapterFlags: Long, val chapterFlags: Long,
@ -99,7 +99,7 @@ data class Manga(
favorite = false, favorite = false,
lastUpdate = 0L, lastUpdate = 0L,
nextUpdate = 0L, nextUpdate = 0L,
calculateInterval = 0, fetchInterval = 0,
dateAdded = 0L, dateAdded = 0L,
viewerFlags = 0L, viewerFlags = 0L,
chapterFlags = 0L, chapterFlags = 0L,

View file

@ -8,7 +8,7 @@ data class MangaUpdate(
val favorite: Boolean? = null, val favorite: Boolean? = null,
val lastUpdate: Long? = null, val lastUpdate: Long? = null,
val nextUpdate: Long? = null, val nextUpdate: Long? = null,
val calculateInterval: Int? = null, val fetchInterval: Int? = null,
val dateAdded: Long? = null, val dateAdded: Long? = null,
val viewerFlags: Long? = null, val viewerFlags: Long? = null,
val chapterFlags: Long? = null, val chapterFlags: Long? = null,
@ -32,7 +32,7 @@ fun Manga.toMangaUpdate(): MangaUpdate {
favorite = favorite, favorite = favorite,
lastUpdate = lastUpdate, lastUpdate = lastUpdate,
nextUpdate = nextUpdate, nextUpdate = nextUpdate,
calculateInterval = calculateInterval, fetchInterval = fetchInterval,
dateAdded = dateAdded, dateAdded = dateAdded,
viewerFlags = viewerFlags, viewerFlags = viewerFlags,
chapterFlags = chapterFlags, chapterFlags = chapterFlags,

View file

@ -23,7 +23,7 @@ interface MangaRepository {
fun getMangaFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> fun getMangaFavoritesBySourceId(sourceId: Long): Flow<List<Manga>>
suspend fun getDuplicateLibraryManga(title: String): Manga? suspend fun getDuplicateLibraryManga(id: Long, title: String): List<Manga>
suspend fun resetMangaViewerFlags(): Boolean suspend fun resetMangaViewerFlags(): Boolean

View file

@ -10,14 +10,14 @@ import java.time.Duration
import java.time.ZonedDateTime import java.time.ZonedDateTime
@Execution(ExecutionMode.CONCURRENT) @Execution(ExecutionMode.CONCURRENT)
class SetAnimeUpdateIntervalTest { class SetAnimeFetchIntervalTest {
private val testTime = ZonedDateTime.parse("2020-01-01T00:00:00Z") private val testTime = ZonedDateTime.parse("2020-01-01T00:00:00Z")
private var episode = Episode.create().copy( private var episode = Episode.create().copy(
dateFetch = testTime.toEpochSecond() * 1000, dateFetch = testTime.toEpochSecond() * 1000,
dateUpload = testTime.toEpochSecond() * 1000, dateUpload = testTime.toEpochSecond() * 1000,
) )
private val setAnimeUpdateInterval = SetAnimeUpdateInterval(mockk()) private val setAnimeFetchInterval = SetAnimeFetchInterval(mockk())
private fun episodeAddTime(episode: Episode, duration: Duration): Episode { private fun episodeAddTime(episode: Episode, duration: Duration): Episode {
val newTime = testTime.plus(duration).toEpochSecond() * 1000 val newTime = testTime.plus(duration).toEpochSecond() * 1000
@ -33,7 +33,7 @@ class SetAnimeUpdateIntervalTest {
val newEpisode = episodeAddTime(episode, duration) val newEpisode = episodeAddTime(episode, duration)
episodes.add(newEpisode) episodes.add(newEpisode)
} }
setAnimeUpdateInterval.calculateInterval(episodes, testTime) shouldBe 7 setAnimeFetchInterval.calculateInterval(episodes, testTime) shouldBe 7
} }
@Test @Test
@ -44,7 +44,7 @@ class SetAnimeUpdateIntervalTest {
val newEpisode = episodeAddTime(episode, duration) val newEpisode = episodeAddTime(episode, duration)
episodes.add(newEpisode) episodes.add(newEpisode)
} }
setAnimeUpdateInterval.calculateInterval(episodes, testTime) shouldBe 7 setAnimeFetchInterval.calculateInterval(episodes, testTime) shouldBe 7
} }
@Test @Test
@ -60,7 +60,7 @@ class SetAnimeUpdateIntervalTest {
val newEpisode = episodeAddTime(episode, duration) val newEpisode = episodeAddTime(episode, duration)
episodes.add(newEpisode) episodes.add(newEpisode)
} }
setAnimeUpdateInterval.calculateInterval(episodes, testTime) shouldBe 7 setAnimeFetchInterval.calculateInterval(episodes, testTime) shouldBe 7
} }
// Default 1 if interval less than 1 // Default 1 if interval less than 1
@ -72,7 +72,7 @@ class SetAnimeUpdateIntervalTest {
val newEpisode = episodeAddTime(episode, duration) val newEpisode = episodeAddTime(episode, duration)
episodes.add(newEpisode) episodes.add(newEpisode)
} }
setAnimeUpdateInterval.calculateInterval(episodes, testTime) shouldBe 1 setAnimeFetchInterval.calculateInterval(episodes, testTime) shouldBe 1
} }
// Normal interval calculation // Normal interval calculation
@ -84,7 +84,7 @@ class SetAnimeUpdateIntervalTest {
val newEpisode = episodeAddTime(episode, duration) val newEpisode = episodeAddTime(episode, duration)
episodes.add(newEpisode) episodes.add(newEpisode)
} }
setAnimeUpdateInterval.calculateInterval(episodes, testTime) shouldBe 1 setAnimeFetchInterval.calculateInterval(episodes, testTime) shouldBe 1
} }
@Test @Test
@ -95,7 +95,7 @@ class SetAnimeUpdateIntervalTest {
val newEpisode = episodeAddTime(episode, duration) val newEpisode = episodeAddTime(episode, duration)
episodes.add(newEpisode) episodes.add(newEpisode)
} }
setAnimeUpdateInterval.calculateInterval(episodes, testTime) shouldBe 2 setAnimeFetchInterval.calculateInterval(episodes, testTime) shouldBe 2
} }
// If interval is decimal, floor to closest integer // If interval is decimal, floor to closest integer
@ -107,7 +107,7 @@ class SetAnimeUpdateIntervalTest {
val newEpisode = episodeAddTime(episode, duration) val newEpisode = episodeAddTime(episode, duration)
episodes.add(newEpisode) episodes.add(newEpisode)
} }
setAnimeUpdateInterval.calculateInterval(episodes, testTime) shouldBe 1 setAnimeFetchInterval.calculateInterval(episodes, testTime) shouldBe 1
} }
@Test @Test
@ -118,7 +118,7 @@ class SetAnimeUpdateIntervalTest {
val newEpisode = episodeAddTime(episode, duration) val newEpisode = episodeAddTime(episode, duration)
episodes.add(newEpisode) episodes.add(newEpisode)
} }
setAnimeUpdateInterval.calculateInterval(episodes, testTime) shouldBe 1 setAnimeFetchInterval.calculateInterval(episodes, testTime) shouldBe 1
} }
// Use fetch time if upload time not available // Use fetch time if upload time not available
@ -130,6 +130,6 @@ class SetAnimeUpdateIntervalTest {
val newEpisode = episodeAddTime(episode, duration).copy(dateUpload = 0L) val newEpisode = episodeAddTime(episode, duration).copy(dateUpload = 0L)
episodes.add(newEpisode) episodes.add(newEpisode)
} }
setAnimeUpdateInterval.calculateInterval(episodes, testTime) shouldBe 1 setAnimeFetchInterval.calculateInterval(episodes, testTime) shouldBe 1
} }
} }

View file

@ -10,14 +10,14 @@ import java.time.Duration
import java.time.ZonedDateTime import java.time.ZonedDateTime
@Execution(ExecutionMode.CONCURRENT) @Execution(ExecutionMode.CONCURRENT)
class SetMangaUpdateIntervalTest { class SetMangaFetchIntervalTest {
private val testTime = ZonedDateTime.parse("2020-01-01T00:00:00Z") private val testTime = ZonedDateTime.parse("2020-01-01T00:00:00Z")
private var chapter = Chapter.create().copy( private var chapter = Chapter.create().copy(
dateFetch = testTime.toEpochSecond() * 1000, dateFetch = testTime.toEpochSecond() * 1000,
dateUpload = testTime.toEpochSecond() * 1000, dateUpload = testTime.toEpochSecond() * 1000,
) )
private val setMangaUpdateInterval = SetMangaUpdateInterval(mockk()) private val setMangaFetchInterval = SetMangaFetchInterval(mockk())
private fun chapterAddTime(chapter: Chapter, duration: Duration): Chapter { private fun chapterAddTime(chapter: Chapter, duration: Duration): Chapter {
val newTime = testTime.plus(duration).toEpochSecond() * 1000 val newTime = testTime.plus(duration).toEpochSecond() * 1000
@ -33,7 +33,7 @@ class SetMangaUpdateIntervalTest {
val newChapter = chapterAddTime(chapter, duration) val newChapter = chapterAddTime(chapter, duration)
chapters.add(newChapter) chapters.add(newChapter)
} }
setMangaUpdateInterval.calculateInterval(chapters, testTime) shouldBe 7 setMangaFetchInterval.calculateInterval(chapters, testTime) shouldBe 7
} }
@Test @Test
@ -44,7 +44,7 @@ class SetMangaUpdateIntervalTest {
val newChapter = chapterAddTime(chapter, duration) val newChapter = chapterAddTime(chapter, duration)
chapters.add(newChapter) chapters.add(newChapter)
} }
setMangaUpdateInterval.calculateInterval(chapters, testTime) shouldBe 7 setMangaFetchInterval.calculateInterval(chapters, testTime) shouldBe 7
} }
@Test @Test
@ -60,7 +60,7 @@ class SetMangaUpdateIntervalTest {
val newChapter = chapterAddTime(chapter, duration) val newChapter = chapterAddTime(chapter, duration)
chapters.add(newChapter) chapters.add(newChapter)
} }
setMangaUpdateInterval.calculateInterval(chapters, testTime) shouldBe 7 setMangaFetchInterval.calculateInterval(chapters, testTime) shouldBe 7
} }
// Default 1 if interval less than 1 // Default 1 if interval less than 1
@ -72,7 +72,7 @@ class SetMangaUpdateIntervalTest {
val newChapter = chapterAddTime(chapter, duration) val newChapter = chapterAddTime(chapter, duration)
chapters.add(newChapter) chapters.add(newChapter)
} }
setMangaUpdateInterval.calculateInterval(chapters, testTime) shouldBe 1 setMangaFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
} }
// Normal interval calculation // Normal interval calculation
@ -84,7 +84,7 @@ class SetMangaUpdateIntervalTest {
val newChapter = chapterAddTime(chapter, duration) val newChapter = chapterAddTime(chapter, duration)
chapters.add(newChapter) chapters.add(newChapter)
} }
setMangaUpdateInterval.calculateInterval(chapters, testTime) shouldBe 1 setMangaFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
} }
@Test @Test
@ -95,7 +95,7 @@ class SetMangaUpdateIntervalTest {
val newChapter = chapterAddTime(chapter, duration) val newChapter = chapterAddTime(chapter, duration)
chapters.add(newChapter) chapters.add(newChapter)
} }
setMangaUpdateInterval.calculateInterval(chapters, testTime) shouldBe 2 setMangaFetchInterval.calculateInterval(chapters, testTime) shouldBe 2
} }
// If interval is decimal, floor to closest integer // If interval is decimal, floor to closest integer
@ -107,7 +107,7 @@ class SetMangaUpdateIntervalTest {
val newChapter = chapterAddTime(chapter, duration) val newChapter = chapterAddTime(chapter, duration)
chapters.add(newChapter) chapters.add(newChapter)
} }
setMangaUpdateInterval.calculateInterval(chapters, testTime) shouldBe 1 setMangaFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
} }
@Test @Test
@ -118,7 +118,7 @@ class SetMangaUpdateIntervalTest {
val newChapter = chapterAddTime(chapter, duration) val newChapter = chapterAddTime(chapter, duration)
chapters.add(newChapter) chapters.add(newChapter)
} }
setMangaUpdateInterval.calculateInterval(chapters, testTime) shouldBe 1 setMangaFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
} }
// Use fetch time if upload time not available // Use fetch time if upload time not available
@ -130,6 +130,6 @@ class SetMangaUpdateIntervalTest {
val newChapter = chapterAddTime(chapter, duration).copy(dateUpload = 0L) val newChapter = chapterAddTime(chapter, duration).copy(dateUpload = 0L)
chapters.add(newChapter) chapters.add(newChapter)
} }
setMangaUpdateInterval.calculateInterval(chapters, testTime) shouldBe 1 setMangaFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
} }
} }

View file

@ -3,7 +3,7 @@ aboutlib_version = "10.8.3"
okhttp_version = "5.0.0-alpha.11" okhttp_version = "5.0.0-alpha.11"
shizuku_version = "12.2.0" shizuku_version = "12.2.0"
sqlite = "2.3.1" sqlite = "2.3.1"
sqldelight = "1.5.5" sqldelight = "2.0.0"
leakcanary = "2.12" leakcanary = "2.12"
voyager = "1.0.0-rc06" voyager = "1.0.0-rc06"
richtext = "0.17.0" richtext = "0.17.0"
@ -76,10 +76,12 @@ shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizu
leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" }
leakcanary-plumber = { module = "com.squareup.leakcanary:plumber-android", version.ref = "leakcanary" } leakcanary-plumber = { module = "com.squareup.leakcanary:plumber-android", version.ref = "leakcanary" }
sqldelight-android-driver = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions-jvm", version.ref = "sqldelight" } sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions-jvm", version.ref = "sqldelight" }
sqldelight-android-paging = { module = "com.squareup.sqldelight:android-paging3-extensions", version.ref = "sqldelight" } sqldelight-android-paging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqldelight" }
sqldelight-gradle = { module = "com.squareup.sqldelight:gradle-plugin", version.ref = "sqldelight" } sqldelight-primitive-adapters = { module = "app.cash.sqldelight:primitive-adapters", version.ref = "sqldelight" }
sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" }
sqldelight-gradle = { module = "app.cash.sqldelight:gradle-plugin", version.ref = "sqldelight" }
junit = "org.junit.jupiter:junit-jupiter:5.10.0" junit = "org.junit.jupiter:junit-jupiter:5.10.0"
kotest-assertions = "io.kotest:kotest-assertions-core:5.6.2" kotest-assertions = "io.kotest:kotest-assertions-core:5.6.2"
@ -106,6 +108,7 @@ js-engine = ["quickjs-android"]
sqlite = ["sqlite-framework", "sqlite-ktx", "sqlite-android"] sqlite = ["sqlite-framework", "sqlite-ktx", "sqlite-android"]
coil = ["coil-core", "coil-gif", "coil-compose"] coil = ["coil-core", "coil-gif", "coil-compose"]
shizuku = ["shizuku-api", "shizuku-provider"] shizuku = ["shizuku-api", "shizuku-provider"]
sqldelight = ["sqldelight-android-driver", "sqldelight-coroutines", "sqldelight-android-paging", "sqldelight-primitive-adapters"]
voyager = ["voyager-navigator", "voyager-tab-navigator", "voyager-transitions"] voyager = ["voyager-navigator", "voyager-tab-navigator", "voyager-transitions"]
richtext = ["richtext-commonmark", "richtext-m3"] richtext = ["richtext-commonmark", "richtext-m3"]
test = ["junit", "kotest-assertions", "mockk"] test = ["junit", "kotest-assertions", "mockk"]

View file

@ -226,7 +226,6 @@
<string name="pref_show_nsfw_source">Show in sources and extensions lists</string> <string name="pref_show_nsfw_source">Show in sources and extensions lists</string>
<string name="parental_controls_info">This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing NSFW (18+) content within the app.</string> <string name="parental_controls_info">This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing NSFW (18+) content within the app.</string>
<string name="recently">Recently</string>
<string name="relative_time_today">Today</string> <string name="relative_time_today">Today</string>
<plurals name="relative_time"> <plurals name="relative_time">
<item quantity="one">Yesterday</item> <item quantity="one">Yesterday</item>
@ -524,6 +523,7 @@
<string name="cache_deleted">Cache cleared. %1$d files have been deleted</string> <string name="cache_deleted">Cache cleared. %1$d files have been deleted</string>
<string name="cache_delete_error">Error occurred while clearing</string> <string name="cache_delete_error">Error occurred while clearing</string>
<string name="pref_invalidate_download_cache">Invalidate downloads index</string> <string name="pref_invalidate_download_cache">Invalidate downloads index</string>
<string name="download_cache_invalidated">Downloads index invalidated</string>
<string name="pref_clear_database">Clear database</string> <string name="pref_clear_database">Clear database</string>
<string name="pref_clear_database_summary">Delete history for entries that are not saved in your library</string> <string name="pref_clear_database_summary">Delete history for entries that are not saved in your library</string>
<string name="clear_database_source_item_count">%1$d non-library entries in database</string> <string name="clear_database_source_item_count">%1$d non-library entries in database</string>

View file

@ -5,7 +5,7 @@ plugins {
} }
kotlin { kotlin {
android() androidTarget()
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {

View file

@ -4,7 +4,7 @@ plugins {
} }
kotlin { kotlin {
android() androidTarget()
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {