mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-24 21:58:34 +03:00
parent
3fba4cbc2b
commit
89777e98b0
62 changed files with 1063 additions and 866 deletions
|
@ -20,8 +20,8 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "xyz.jmir.tachiyomi.mi"
|
applicationId = "xyz.jmir.tachiyomi.mi"
|
||||||
|
|
||||||
versionCode = 106
|
versionCode = 108
|
||||||
versionName = "0.14.6"
|
versionName = "0.14.7"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
|
@ -319,12 +319,12 @@ tasks {
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-P",
|
"-P",
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||||
project.buildDir.absolutePath + "/compose_metrics",
|
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||||
)
|
)
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-P",
|
"-P",
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||||
project.buildDir.absolutePath + "/compose_metrics",
|
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,15 +113,15 @@ import tachiyomi.domain.history.manga.interactor.RemoveMangaHistory
|
||||||
import tachiyomi.domain.history.manga.interactor.UpsertMangaHistory
|
import tachiyomi.domain.history.manga.interactor.UpsertMangaHistory
|
||||||
import tachiyomi.domain.history.manga.repository.MangaHistoryRepository
|
import tachiyomi.domain.history.manga.repository.MangaHistoryRepository
|
||||||
import tachiyomi.domain.items.chapter.interactor.GetChapter
|
import tachiyomi.domain.items.chapter.interactor.GetChapter
|
||||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
|
|
||||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByUrlAndMangaId
|
import tachiyomi.domain.items.chapter.interactor.GetChapterByUrlAndMangaId
|
||||||
|
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.items.chapter.interactor.SetMangaDefaultChapterFlags
|
import tachiyomi.domain.items.chapter.interactor.SetMangaDefaultChapterFlags
|
||||||
import tachiyomi.domain.items.chapter.interactor.ShouldUpdateDbChapter
|
import tachiyomi.domain.items.chapter.interactor.ShouldUpdateDbChapter
|
||||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.items.chapter.repository.ChapterRepository
|
import tachiyomi.domain.items.chapter.repository.ChapterRepository
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisode
|
import tachiyomi.domain.items.episode.interactor.GetEpisode
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByUrlAndAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodeByUrlAndAnimeId
|
||||||
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.items.episode.interactor.SetAnimeDefaultEpisodeFlags
|
import tachiyomi.domain.items.episode.interactor.SetAnimeDefaultEpisodeFlags
|
||||||
import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode
|
import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode
|
||||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||||
|
@ -250,7 +250,7 @@ class DomainModule : InjektModule {
|
||||||
|
|
||||||
addSingletonFactory<EpisodeRepository> { EpisodeRepositoryImpl(get()) }
|
addSingletonFactory<EpisodeRepository> { EpisodeRepositoryImpl(get()) }
|
||||||
addFactory { GetEpisode(get()) }
|
addFactory { GetEpisode(get()) }
|
||||||
addFactory { GetEpisodeByAnimeId(get()) }
|
addFactory { GetEpisodesByAnimeId(get()) }
|
||||||
addFactory { GetEpisodeByUrlAndAnimeId(get()) }
|
addFactory { GetEpisodeByUrlAndAnimeId(get()) }
|
||||||
addFactory { UpdateEpisode(get()) }
|
addFactory { UpdateEpisode(get()) }
|
||||||
addFactory { SetSeenStatus(get(), get(), get(), get()) }
|
addFactory { SetSeenStatus(get(), get(), get(), get()) }
|
||||||
|
@ -259,7 +259,7 @@ class DomainModule : InjektModule {
|
||||||
|
|
||||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||||
addFactory { GetChapter(get()) }
|
addFactory { GetChapter(get()) }
|
||||||
addFactory { GetChapterByMangaId(get()) }
|
addFactory { GetChaptersByMangaId(get()) }
|
||||||
addFactory { GetChapterByUrlAndMangaId(get()) }
|
addFactory { GetChapterByUrlAndMangaId(get()) }
|
||||||
addFactory { UpdateChapter(get()) }
|
addFactory { UpdateChapter(get()) }
|
||||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
class BasePreferences(
|
class BasePreferences(
|
||||||
|
@ -14,9 +15,12 @@ class BasePreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun downloadedOnly() = preferenceStore.getBoolean("pref_downloaded_only", false)
|
fun downloadedOnly() = preferenceStore.getBoolean(
|
||||||
|
Preference.appStateKey("pref_downloaded_only"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
fun incognitoMode() = preferenceStore.getBoolean("incognito_mode", false)
|
fun incognitoMode() = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false)
|
||||||
|
|
||||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import tachiyomi.data.items.chapter.ChapterSanitizer
|
import tachiyomi.data.items.chapter.ChapterSanitizer
|
||||||
import tachiyomi.domain.entries.manga.model.Manga
|
import tachiyomi.domain.entries.manga.model.Manga
|
||||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.items.chapter.interactor.ShouldUpdateDbChapter
|
import tachiyomi.domain.items.chapter.interactor.ShouldUpdateDbChapter
|
||||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
import tachiyomi.domain.items.chapter.model.Chapter
|
||||||
|
@ -20,7 +20,6 @@ import tachiyomi.domain.items.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.items.chapter.repository.ChapterRepository
|
import tachiyomi.domain.items.chapter.repository.ChapterRepository
|
||||||
import tachiyomi.domain.items.chapter.service.ChapterRecognition
|
import tachiyomi.domain.items.chapter.service.ChapterRecognition
|
||||||
import tachiyomi.source.local.entries.manga.isLocal
|
import tachiyomi.source.local.entries.manga.isLocal
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.lang.Long.max
|
import java.lang.Long.max
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
@ -33,7 +32,7 @@ class SyncChaptersWithSource(
|
||||||
private val shouldUpdateDbChapter: ShouldUpdateDbChapter,
|
private val shouldUpdateDbChapter: ShouldUpdateDbChapter,
|
||||||
private val updateManga: UpdateManga,
|
private val updateManga: UpdateManga,
|
||||||
private val updateChapter: UpdateChapter,
|
private val updateChapter: UpdateChapter,
|
||||||
private val getChapterByMangaId: GetChapterByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,7 +66,7 @@ class SyncChaptersWithSource(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters from db.
|
// Chapters from db.
|
||||||
val dbChapters = getChapterByMangaId.await(manga.id)
|
val dbChapters = getChaptersByMangaId.await(manga.id)
|
||||||
|
|
||||||
// Chapters from the source not in db.
|
// Chapters from the source not in db.
|
||||||
val toAdd = mutableListOf<Chapter>()
|
val toAdd = mutableListOf<Chapter>()
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.domain.items.chapter.model
|
package eu.kanade.domain.items.chapter.model
|
||||||
|
|
||||||
import data.Chapters
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.manga.ChapterImpl
|
import eu.kanade.tachiyomi.data.database.models.manga.ChapterImpl
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
import tachiyomi.domain.items.chapter.model.Chapter
|
||||||
|
@ -27,16 +26,6 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Chapter.copyFrom(other: Chapters): Chapter {
|
|
||||||
return copy(
|
|
||||||
name = other.name,
|
|
||||||
url = other.url,
|
|
||||||
dateUpload = other.date_upload,
|
|
||||||
chapterNumber = other.chapter_number,
|
|
||||||
scanlator = other.scanlator?.ifBlank { null },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
||||||
it.id = id
|
it.id = id
|
||||||
it.manga_id = mangaId
|
it.manga_id = mangaId
|
||||||
|
|
|
@ -2,7 +2,7 @@ package eu.kanade.domain.items.chapter.model
|
||||||
|
|
||||||
import eu.kanade.domain.entries.manga.model.downloadedFilter
|
import eu.kanade.domain.entries.manga.model.downloadedFilter
|
||||||
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager
|
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager
|
||||||
import eu.kanade.tachiyomi.ui.entries.manga.ChapterItem
|
import eu.kanade.tachiyomi.ui.entries.manga.ChapterList
|
||||||
import tachiyomi.domain.entries.applyFilter
|
import tachiyomi.domain.entries.applyFilter
|
||||||
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
|
||||||
|
@ -39,7 +39,7 @@ fun List<Chapter>.applyFilters(manga: Manga, downloadManager: MangaDownloadManag
|
||||||
* Applies the view filters to the list of chapters obtained from the database.
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
* @return an observable of the list of chapters filtered and sorted.
|
* @return an observable of the list of chapters filtered and sorted.
|
||||||
*/
|
*/
|
||||||
fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> {
|
fun List<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item> {
|
||||||
val isLocalManga = manga.isLocal()
|
val isLocalManga = manga.isLocal()
|
||||||
val unreadFilter = manga.unreadFilter
|
val unreadFilter = manga.unreadFilter
|
||||||
val downloadedFilter = manga.downloadedFilter
|
val downloadedFilter = manga.downloadedFilter
|
||||||
|
|
|
@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadProvider
|
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadProvider
|
||||||
import tachiyomi.data.items.episode.EpisodeSanitizer
|
import tachiyomi.data.items.episode.EpisodeSanitizer
|
||||||
import tachiyomi.domain.entries.anime.model.Anime
|
import tachiyomi.domain.entries.anime.model.Anime
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode
|
import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode
|
||||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
|
@ -20,7 +20,6 @@ import tachiyomi.domain.items.episode.model.toEpisodeUpdate
|
||||||
import tachiyomi.domain.items.episode.repository.EpisodeRepository
|
import tachiyomi.domain.items.episode.repository.EpisodeRepository
|
||||||
import tachiyomi.domain.items.episode.service.EpisodeRecognition
|
import tachiyomi.domain.items.episode.service.EpisodeRecognition
|
||||||
import tachiyomi.source.local.entries.anime.isLocal
|
import tachiyomi.source.local.entries.anime.isLocal
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.lang.Long.max
|
import java.lang.Long.max
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
@ -33,7 +32,7 @@ class SyncEpisodesWithSource(
|
||||||
private val shouldUpdateDbEpisode: ShouldUpdateDbEpisode,
|
private val shouldUpdateDbEpisode: ShouldUpdateDbEpisode,
|
||||||
private val updateAnime: UpdateAnime,
|
private val updateAnime: UpdateAnime,
|
||||||
private val updateEpisode: UpdateEpisode,
|
private val updateEpisode: UpdateEpisode,
|
||||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId,
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,7 +66,7 @@ class SyncEpisodesWithSource(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Episodes from db.
|
// Episodes from db.
|
||||||
val dbEpisodes = getEpisodeByAnimeId.await(anime.id)
|
val dbEpisodes = getEpisodesByAnimeId.await(anime.id)
|
||||||
|
|
||||||
// Episodes from the source not in db.
|
// Episodes from the source not in db.
|
||||||
val toAdd = mutableListOf<Episode>()
|
val toAdd = mutableListOf<Episode>()
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.domain.items.episode.model
|
package eu.kanade.domain.items.episode.model
|
||||||
|
|
||||||
import dataanime.Episodes
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
import eu.kanade.tachiyomi.data.database.models.anime.EpisodeImpl
|
import eu.kanade.tachiyomi.data.database.models.anime.EpisodeImpl
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
|
@ -27,16 +26,6 @@ fun Episode.copyFromSEpisode(sEpisode: SEpisode): Episode {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Episode.copyFrom(other: Episodes): Episode {
|
|
||||||
return copy(
|
|
||||||
name = other.name,
|
|
||||||
url = other.url,
|
|
||||||
dateUpload = other.date_upload,
|
|
||||||
episodeNumber = other.episode_number,
|
|
||||||
scanlator = other.scanlator?.ifBlank { null },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Episode.toDbEpisode(): DbEpisode = EpisodeImpl().also {
|
fun Episode.toDbEpisode(): DbEpisode = EpisodeImpl().also {
|
||||||
it.id = id
|
it.id = id
|
||||||
it.anime_id = animeId
|
it.anime_id = animeId
|
||||||
|
|
|
@ -2,7 +2,7 @@ package eu.kanade.domain.items.episode.model
|
||||||
|
|
||||||
import eu.kanade.domain.entries.anime.model.downloadedFilter
|
import eu.kanade.domain.entries.anime.model.downloadedFilter
|
||||||
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
||||||
import eu.kanade.tachiyomi.ui.entries.anime.EpisodeItem
|
import eu.kanade.tachiyomi.ui.entries.anime.EpisodeList
|
||||||
import tachiyomi.domain.entries.anime.model.Anime
|
import tachiyomi.domain.entries.anime.model.Anime
|
||||||
import tachiyomi.domain.entries.applyFilter
|
import tachiyomi.domain.entries.applyFilter
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
|
@ -39,7 +39,7 @@ fun List<Episode>.applyFilters(anime: Anime, downloadManager: AnimeDownloadManag
|
||||||
* Applies the view filters to the list of episodes obtained from the database.
|
* Applies the view filters to the list of episodes obtained from the database.
|
||||||
* @return an observable of the list of episodes filtered and sorted.
|
* @return an observable of the list of episodes filtered and sorted.
|
||||||
*/
|
*/
|
||||||
fun List<EpisodeItem>.applyFilters(anime: Anime): Sequence<EpisodeItem> {
|
fun List<EpisodeList.Item>.applyFilters(anime: Anime): Sequence<EpisodeList.Item> {
|
||||||
val isLocalAnime = anime.isLocal()
|
val isLocalAnime = anime.isLocal()
|
||||||
val unseenFilter = anime.unseenFilter
|
val unseenFilter = anime.unseenFilter
|
||||||
val downloadedFilter = anime.downloadedFilter
|
val downloadedFilter = anime.downloadedFilter
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.kanade.domain.source.service
|
package eu.kanade.domain.source.service
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
@ -35,7 +36,7 @@ class SourcePreferences(
|
||||||
SetMigrateSorting.Direction.ASCENDING,
|
SetMigrateSorting.Direction.ASCENDING,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
|
fun trustedSignatures() = preferenceStore.getStringSet(Preference.appStateKey("trusted_signatures"), emptySet())
|
||||||
|
|
||||||
// Mixture Sources
|
// Mixture Sources
|
||||||
|
|
||||||
|
@ -45,8 +46,14 @@ class SourcePreferences(
|
||||||
fun pinnedAnimeSources() = preferenceStore.getStringSet("pinned_anime_catalogues", emptySet())
|
fun pinnedAnimeSources() = preferenceStore.getStringSet("pinned_anime_catalogues", emptySet())
|
||||||
fun pinnedMangaSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
fun pinnedMangaSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||||
|
|
||||||
fun lastUsedAnimeSource() = preferenceStore.getLong("last_anime_catalogue_source", -1)
|
fun lastUsedAnimeSource() = preferenceStore.getLong(
|
||||||
fun lastUsedMangaSource() = preferenceStore.getLong("last_catalogue_source", -1)
|
Preference.appStateKey("last_anime_catalogue_source"),
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
fun lastUsedMangaSource() = preferenceStore.getLong(
|
||||||
|
Preference.appStateKey("last_catalogue_source"),
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
|
||||||
fun animeExtensionUpdatesCount() = preferenceStore.getInt("animeext_updates_count", 0)
|
fun animeExtensionUpdatesCount() = preferenceStore.getInt("animeext_updates_count", 0)
|
||||||
fun mangaExtensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
fun mangaExtensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.track.AnimeTracker
|
||||||
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTracker
|
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTracker
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||||
import tachiyomi.domain.items.episode.model.toEpisodeUpdate
|
import tachiyomi.domain.items.episode.model.toEpisodeUpdate
|
||||||
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
||||||
|
@ -14,7 +14,7 @@ import tachiyomi.domain.track.anime.model.AnimeTrack
|
||||||
class SyncEpisodeProgressWithTrack(
|
class SyncEpisodeProgressWithTrack(
|
||||||
private val updateEpisode: UpdateEpisode,
|
private val updateEpisode: UpdateEpisode,
|
||||||
private val insertTrack: InsertAnimeTrack,
|
private val insertTrack: InsertAnimeTrack,
|
||||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId,
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(
|
suspend fun await(
|
||||||
|
@ -26,7 +26,7 @@ class SyncEpisodeProgressWithTrack(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val sortedEpisodes = getEpisodeByAnimeId.await(animeId)
|
val sortedEpisodes = getEpisodesByAnimeId.await(animeId)
|
||||||
.sortedBy { it.episodeNumber }
|
.sortedBy { it.episodeNumber }
|
||||||
.filter { it.isRecognizedNumber }
|
.filter { it.isRecognizedNumber }
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.track.EnhancedMangaTracker
|
||||||
import eu.kanade.tachiyomi.data.track.MangaTracker
|
import eu.kanade.tachiyomi.data.track.MangaTracker
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.items.chapter.model.toChapterUpdate
|
import tachiyomi.domain.items.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
||||||
|
@ -14,7 +14,7 @@ import tachiyomi.domain.track.manga.model.MangaTrack
|
||||||
class SyncChapterProgressWithTrack(
|
class SyncChapterProgressWithTrack(
|
||||||
private val updateChapter: UpdateChapter,
|
private val updateChapter: UpdateChapter,
|
||||||
private val insertTrack: InsertMangaTrack,
|
private val insertTrack: InsertMangaTrack,
|
||||||
private val getChapterByMangaId: GetChapterByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(
|
suspend fun await(
|
||||||
|
@ -26,7 +26,7 @@ class SyncChapterProgressWithTrack(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val sortedChapters = getChapterByMangaId.await(mangaId)
|
val sortedChapters = getChaptersByMangaId.await(mangaId)
|
||||||
.sortedBy { it.chapterNumber }
|
.sortedBy { it.chapterNumber }
|
||||||
.filter { it.isRecognizedNumber }
|
.filter { it.isRecognizedNumber }
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.TabRow
|
import androidx.compose.material3.TabRow
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
@ -33,6 +32,7 @@ import eu.kanade.tachiyomi.R
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.HorizontalPager
|
import tachiyomi.presentation.core.components.HorizontalPager
|
||||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
import tachiyomi.presentation.core.components.material.TabIndicator
|
||||||
|
import tachiyomi.presentation.core.components.material.TabText
|
||||||
|
|
||||||
object TabbedDialogPaddings {
|
object TabbedDialogPaddings {
|
||||||
val Horizontal = 24.dp
|
val Horizontal = 24.dp
|
||||||
|
@ -71,21 +71,12 @@ fun TabbedDialog(
|
||||||
},
|
},
|
||||||
divider = {},
|
divider = {},
|
||||||
) {
|
) {
|
||||||
tabTitles.fastForEachIndexed { i, tab ->
|
tabTitles.fastForEachIndexed { index, tab ->
|
||||||
val selected = pagerState.currentPage == i
|
|
||||||
Tab(
|
Tab(
|
||||||
selected = selected,
|
selected = pagerState.currentPage == index,
|
||||||
onClick = { scope.launch { pagerState.animateScrollToPage(i) } },
|
onClick = { scope.launch { pagerState.animateScrollToPage(index) } },
|
||||||
text = {
|
text = { TabText(text = tab) },
|
||||||
Text(
|
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
text = tab,
|
|
||||||
color = if (selected) {
|
|
||||||
MaterialTheme.colorScheme.primary
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@ import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
@ -27,7 +29,9 @@ import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
@ -47,6 +51,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.util.fastAll
|
import androidx.compose.ui.util.fastAll
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
|
@ -73,7 +78,7 @@ import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload
|
||||||
import eu.kanade.tachiyomi.source.anime.getNameForAnimeInfo
|
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.EpisodeList
|
||||||
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
|
||||||
|
@ -87,8 +92,10 @@ import tachiyomi.presentation.core.components.VerticalFastScroller
|
||||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
import tachiyomi.presentation.core.util.isScrollingUp
|
import tachiyomi.presentation.core.util.isScrollingUp
|
||||||
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -107,7 +114,7 @@ fun AnimeScreen(
|
||||||
alwaysUseExternalPlayer: Boolean,
|
alwaysUseExternalPlayer: Boolean,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onEpisodeClicked: (episode: Episode, alt: Boolean) -> Unit,
|
onEpisodeClicked: (episode: Episode, alt: Boolean) -> Unit,
|
||||||
onDownloadEpisode: ((List<EpisodeItem>, EpisodeDownloadAction) -> Unit)?,
|
onDownloadEpisode: ((List<EpisodeList.Item>, EpisodeDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
|
@ -139,10 +146,10 @@ fun AnimeScreen(
|
||||||
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
||||||
|
|
||||||
// For episode swipe
|
// For episode swipe
|
||||||
onEpisodeSwipe: (EpisodeItem, LibraryPreferences.EpisodeSwipeAction) -> Unit,
|
onEpisodeSwipe: (EpisodeList.Item, LibraryPreferences.EpisodeSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Episode selection
|
// Episode selection
|
||||||
onEpisodeSelected: (EpisodeItem, Boolean, Boolean, Boolean) -> Unit,
|
onEpisodeSelected: (EpisodeList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onAllEpisodeSelected: (Boolean) -> Unit,
|
onAllEpisodeSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -257,7 +264,7 @@ private fun AnimeScreenSmallImpl(
|
||||||
alwaysUseExternalPlayer: Boolean,
|
alwaysUseExternalPlayer: Boolean,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onEpisodeClicked: (Episode, Boolean) -> Unit,
|
onEpisodeClicked: (Episode, Boolean) -> Unit,
|
||||||
onDownloadEpisode: ((List<EpisodeItem>, EpisodeDownloadAction) -> Unit)?,
|
onDownloadEpisode: ((List<EpisodeList.Item>, EpisodeDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
|
@ -291,16 +298,17 @@ private fun AnimeScreenSmallImpl(
|
||||||
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
||||||
|
|
||||||
// For episode swipe
|
// For episode swipe
|
||||||
onEpisodeSwipe: (EpisodeItem, LibraryPreferences.EpisodeSwipeAction) -> Unit,
|
onEpisodeSwipe: (EpisodeList.Item, LibraryPreferences.EpisodeSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Episode selection
|
// Episode selection
|
||||||
onEpisodeSelected: (EpisodeItem, Boolean, Boolean, Boolean) -> Unit,
|
onEpisodeSelected: (EpisodeList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onAllEpisodeSelected: (Boolean) -> Unit,
|
onAllEpisodeSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val episodeListState = rememberLazyListState()
|
val episodeListState = rememberLazyListState()
|
||||||
|
|
||||||
val episodes = remember(state) { state.processedEpisodes }
|
val episodes = remember(state) { state.processedEpisodes }
|
||||||
|
val listItem = remember(state) { state.episodeListItems }
|
||||||
|
|
||||||
val isAnySelected by remember {
|
val isAnySelected by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
|
@ -516,7 +524,8 @@ private fun AnimeScreenSmallImpl(
|
||||||
|
|
||||||
sharedEpisodeItems(
|
sharedEpisodeItems(
|
||||||
anime = state.anime,
|
anime = state.anime,
|
||||||
episodes = episodes,
|
episodes = listItem,
|
||||||
|
isAnyEpisodeSelected = episodes.fastAny { it.selected },
|
||||||
dateRelativeTime = dateRelativeTime,
|
dateRelativeTime = dateRelativeTime,
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
episodeSwipeStartAction = episodeSwipeStartAction,
|
episodeSwipeStartAction = episodeSwipeStartAction,
|
||||||
|
@ -546,7 +555,7 @@ fun AnimeScreenLargeImpl(
|
||||||
alwaysUseExternalPlayer: Boolean,
|
alwaysUseExternalPlayer: Boolean,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onEpisodeClicked: (Episode, Boolean) -> Unit,
|
onEpisodeClicked: (Episode, Boolean) -> Unit,
|
||||||
onDownloadEpisode: ((List<EpisodeItem>, EpisodeDownloadAction) -> Unit)?,
|
onDownloadEpisode: ((List<EpisodeList.Item>, EpisodeDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
|
@ -580,10 +589,10 @@ fun AnimeScreenLargeImpl(
|
||||||
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
||||||
|
|
||||||
// For swipe actions
|
// For swipe actions
|
||||||
onEpisodeSwipe: (EpisodeItem, LibraryPreferences.EpisodeSwipeAction) -> Unit,
|
onEpisodeSwipe: (EpisodeList.Item, LibraryPreferences.EpisodeSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Episode selection
|
// Episode selection
|
||||||
onEpisodeSelected: (EpisodeItem, Boolean, Boolean, Boolean) -> Unit,
|
onEpisodeSelected: (EpisodeList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onAllEpisodeSelected: (Boolean) -> Unit,
|
onAllEpisodeSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -591,6 +600,7 @@ fun AnimeScreenLargeImpl(
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
val episodes = remember(state) { state.processedEpisodes }
|
val episodes = remember(state) { state.processedEpisodes }
|
||||||
|
val listItem = remember(state) { state.episodeListItems }
|
||||||
|
|
||||||
val isAnySelected by remember {
|
val isAnySelected by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
|
@ -799,7 +809,8 @@ fun AnimeScreenLargeImpl(
|
||||||
|
|
||||||
sharedEpisodeItems(
|
sharedEpisodeItems(
|
||||||
anime = state.anime,
|
anime = state.anime,
|
||||||
episodes = episodes,
|
episodes = listItem,
|
||||||
|
isAnyEpisodeSelected = episodes.fastAny { it.selected },
|
||||||
dateRelativeTime = dateRelativeTime,
|
dateRelativeTime = dateRelativeTime,
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
episodeSwipeStartAction = episodeSwipeStartAction,
|
episodeSwipeStartAction = episodeSwipeStartAction,
|
||||||
|
@ -819,13 +830,13 @@ fun AnimeScreenLargeImpl(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SharedAnimeBottomActionMenu(
|
private fun SharedAnimeBottomActionMenu(
|
||||||
selected: List<EpisodeItem>,
|
selected: List<EpisodeList.Item>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onEpisodeClicked: (Episode, Boolean) -> Unit,
|
onEpisodeClicked: (Episode, Boolean) -> Unit,
|
||||||
onMultiBookmarkClicked: (List<Episode>, bookmarked: Boolean) -> Unit,
|
onMultiBookmarkClicked: (List<Episode>, bookmarked: Boolean) -> Unit,
|
||||||
onMultiMarkAsSeenClicked: (List<Episode>, markAsSeen: Boolean) -> Unit,
|
onMultiMarkAsSeenClicked: (List<Episode>, markAsSeen: Boolean) -> Unit,
|
||||||
onMarkPreviousAsSeenClicked: (Episode) -> Unit,
|
onMarkPreviousAsSeenClicked: (Episode) -> Unit,
|
||||||
onDownloadEpisode: ((List<EpisodeItem>, EpisodeDownloadAction) -> Unit)?,
|
onDownloadEpisode: ((List<EpisodeList.Item>, EpisodeDownloadAction) -> Unit)?,
|
||||||
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
||||||
fillFraction: Float,
|
fillFraction: Float,
|
||||||
alwaysUseExternalPlayer: Boolean,
|
alwaysUseExternalPlayer: Boolean,
|
||||||
|
@ -870,34 +881,63 @@ private fun SharedAnimeBottomActionMenu(
|
||||||
|
|
||||||
private fun LazyListScope.sharedEpisodeItems(
|
private fun LazyListScope.sharedEpisodeItems(
|
||||||
anime: Anime,
|
anime: Anime,
|
||||||
episodes: List<EpisodeItem>,
|
episodes: List<EpisodeList>,
|
||||||
|
isAnyEpisodeSelected: Boolean,
|
||||||
dateRelativeTime: Boolean,
|
dateRelativeTime: Boolean,
|
||||||
dateFormat: DateFormat,
|
dateFormat: DateFormat,
|
||||||
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
|
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
|
||||||
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
|
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
|
||||||
onEpisodeClicked: (Episode, Boolean) -> Unit,
|
onEpisodeClicked: (Episode, Boolean) -> Unit,
|
||||||
onDownloadEpisode: ((List<EpisodeItem>, EpisodeDownloadAction) -> Unit)?,
|
onDownloadEpisode: ((List<EpisodeList.Item>, EpisodeDownloadAction) -> Unit)?,
|
||||||
onEpisodeSelected: (EpisodeItem, Boolean, Boolean, Boolean) -> Unit,
|
onEpisodeSelected: (EpisodeList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onEpisodeSwipe: (EpisodeItem, LibraryPreferences.EpisodeSwipeAction) -> Unit,
|
onEpisodeSwipe: (EpisodeList.Item, LibraryPreferences.EpisodeSwipeAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = episodes,
|
items = episodes,
|
||||||
key = { "episode-${it.episode.id}" },
|
key = { item ->
|
||||||
|
when (item) {
|
||||||
|
is EpisodeList.MissingCount -> "missing-count-${item.id}"
|
||||||
|
is EpisodeList.Item -> "episode-${item.id}"
|
||||||
|
}
|
||||||
|
},
|
||||||
contentType = { EntryScreenItem.ITEM },
|
contentType = { EntryScreenItem.ITEM },
|
||||||
) { episodeItem ->
|
) { item ->
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
when (item) {
|
||||||
|
is EpisodeList.MissingCount -> {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
text = pluralStringResource(
|
||||||
|
id = R.plurals.missing_items,
|
||||||
|
count = item.count,
|
||||||
|
item.count,
|
||||||
|
),
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
)
|
||||||
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is EpisodeList.Item -> {
|
||||||
AnimeEpisodeListItem(
|
AnimeEpisodeListItem(
|
||||||
title = if (anime.displayMode == Anime.EPISODE_DISPLAY_NUMBER) {
|
title = if (anime.displayMode == Anime.EPISODE_DISPLAY_NUMBER) {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.display_mode_episode,
|
R.string.display_mode_episode,
|
||||||
formatEpisodeNumber(episodeItem.episode.episodeNumber),
|
formatEpisodeNumber(item.episode.episodeNumber),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
episodeItem.episode.name
|
item.episode.name
|
||||||
},
|
},
|
||||||
date = episodeItem.episode.dateUpload
|
date = item.episode.dateUpload
|
||||||
.takeIf { it > 0L }
|
.takeIf { it > 0L }
|
||||||
?.let {
|
?.let {
|
||||||
Date(it).toRelativeString(
|
Date(it).toRelativeString(
|
||||||
|
@ -906,64 +946,58 @@ private fun LazyListScope.sharedEpisodeItems(
|
||||||
dateFormat,
|
dateFormat,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
watchProgress = episodeItem.episode.lastSecondSeen
|
watchProgress = item.episode.lastSecondSeen
|
||||||
.takeIf { !episodeItem.episode.seen && it > 0L }
|
.takeIf { !item.episode.seen && it > 0L }
|
||||||
?.let {
|
?.let {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.episode_progress,
|
R.string.episode_progress,
|
||||||
formatTime(it),
|
it + 1,
|
||||||
formatTime(episodeItem.episode.totalSeconds),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scanlator = episodeItem.episode.scanlator.takeIf { !it.isNullOrBlank() },
|
scanlator = item.episode.scanlator.takeIf { !it.isNullOrBlank() },
|
||||||
seen = episodeItem.episode.seen,
|
seen = item.episode.seen,
|
||||||
bookmark = episodeItem.episode.bookmark,
|
bookmark = item.episode.bookmark,
|
||||||
selected = episodeItem.selected,
|
selected = item.selected,
|
||||||
downloadIndicatorEnabled = episodes.fastAll { !it.selected },
|
downloadIndicatorEnabled = !isAnyEpisodeSelected,
|
||||||
downloadStateProvider = { episodeItem.downloadState },
|
downloadStateProvider = { item.downloadState },
|
||||||
downloadProgressProvider = { episodeItem.downloadProgress },
|
downloadProgressProvider = { item.downloadProgress },
|
||||||
episodeSwipeStartAction = episodeSwipeStartAction,
|
episodeSwipeStartAction = episodeSwipeStartAction,
|
||||||
episodeSwipeEndAction = episodeSwipeEndAction,
|
episodeSwipeEndAction = episodeSwipeEndAction,
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
onEpisodeSelected(episodeItem, !episodeItem.selected, true, true)
|
onEpisodeSelected(item, !item.selected, true, true)
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
onEpisodeItemClick(
|
onEpisodeItemClick(
|
||||||
episodeItem = episodeItem,
|
episodeItem = item,
|
||||||
episodes = episodes,
|
isAnyEpisodeSelected = isAnyEpisodeSelected,
|
||||||
onToggleSelection = {
|
onToggleSelection = { onEpisodeSelected(item, !item.selected, true, false) },
|
||||||
onEpisodeSelected(
|
|
||||||
episodeItem,
|
|
||||||
!episodeItem.selected,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onEpisodeClicked = onEpisodeClicked,
|
onEpisodeClicked = onEpisodeClicked,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onDownloadClick = if (onDownloadEpisode != null) {
|
onDownloadClick = if (onDownloadEpisode != null) {
|
||||||
{ onDownloadEpisode(listOf(episodeItem), it) }
|
{ onDownloadEpisode(listOf(item), it) }
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
},
|
},
|
||||||
onEpisodeSwipe = {
|
onEpisodeSwipe = {
|
||||||
onEpisodeSwipe(episodeItem, it)
|
onEpisodeSwipe(item, it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onEpisodeItemClick(
|
private fun onEpisodeItemClick(
|
||||||
episodeItem: EpisodeItem,
|
episodeItem: EpisodeList.Item,
|
||||||
episodes: List<EpisodeItem>,
|
isAnyEpisodeSelected: Boolean,
|
||||||
onToggleSelection: (Boolean) -> Unit,
|
onToggleSelection: (Boolean) -> Unit,
|
||||||
onEpisodeClicked: (Episode, Boolean) -> Unit,
|
onEpisodeClicked: (Episode, Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
episodeItem.selected -> onToggleSelection(false)
|
episodeItem.selected -> onToggleSelection(false)
|
||||||
episodes.fastAny { it.selected } -> onToggleSelection(true)
|
isAnyEpisodeSelected -> onToggleSelection(true)
|
||||||
else -> onEpisodeClicked(episodeItem.episode, false)
|
else -> onEpisodeClicked(episodeItem.episode, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.presentation.entries.anime.components
|
package eu.kanade.presentation.entries.anime.components
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
@ -29,6 +28,7 @@ import androidx.compose.material.icons.filled.Brush
|
||||||
import androidx.compose.material.icons.filled.Favorite
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
import androidx.compose.material.icons.filled.HourglassEmpty
|
import androidx.compose.material.icons.filled.HourglassEmpty
|
||||||
import androidx.compose.material.icons.filled.PersonOutline
|
import androidx.compose.material.icons.filled.PersonOutline
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material.icons.outlined.AttachMoney
|
import androidx.compose.material.icons.outlined.AttachMoney
|
||||||
import androidx.compose.material.icons.outlined.Block
|
import androidx.compose.material.icons.outlined.Block
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
|
@ -43,6 +43,7 @@ import androidx.compose.material.icons.outlined.Warning
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProvideTextStyle
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
import androidx.compose.material3.SuggestionChip
|
import androidx.compose.material3.SuggestionChip
|
||||||
|
@ -128,7 +129,7 @@ fun AnimeInfoBox(
|
||||||
.alpha(.2f),
|
.alpha(.2f),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manga & source info
|
// Anime & source info
|
||||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||||
if (!isTabletUi) {
|
if (!isTabletUi) {
|
||||||
AnimeAndSourceTitlesSmall(
|
AnimeAndSourceTitlesSmall(
|
||||||
|
@ -136,7 +137,6 @@ fun AnimeInfoBox(
|
||||||
coverDataProvider = coverDataProvider,
|
coverDataProvider = coverDataProvider,
|
||||||
onCoverClick = onCoverClick,
|
onCoverClick = onCoverClick,
|
||||||
title = title,
|
title = title,
|
||||||
context = LocalContext.current,
|
|
||||||
doSearch = doSearch,
|
doSearch = doSearch,
|
||||||
author = author,
|
author = author,
|
||||||
artist = artist,
|
artist = artist,
|
||||||
|
@ -150,7 +150,6 @@ fun AnimeInfoBox(
|
||||||
coverDataProvider = coverDataProvider,
|
coverDataProvider = coverDataProvider,
|
||||||
onCoverClick = onCoverClick,
|
onCoverClick = onCoverClick,
|
||||||
title = title,
|
title = title,
|
||||||
context = LocalContext.current,
|
|
||||||
doSearch = doSearch,
|
doSearch = doSearch,
|
||||||
author = author,
|
author = author,
|
||||||
artist = artist,
|
artist = artist,
|
||||||
|
@ -335,7 +334,6 @@ private fun AnimeAndSourceTitlesLarge(
|
||||||
coverDataProvider: () -> Anime,
|
coverDataProvider: () -> Anime,
|
||||||
onCoverClick: () -> Unit,
|
onCoverClick: () -> Unit,
|
||||||
title: String,
|
title: String,
|
||||||
context: Context,
|
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
author: String?,
|
author: String?,
|
||||||
artist: String?,
|
artist: String?,
|
||||||
|
@ -356,104 +354,16 @@ private fun AnimeAndSourceTitlesLarge(
|
||||||
onClick = onCoverClick,
|
onClick = onCoverClick,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(
|
AnimeContentInfo(
|
||||||
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
title = title,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
doSearch = doSearch,
|
||||||
modifier = Modifier.clickableNoIndication(
|
author = author,
|
||||||
onLongClick = { if (title.isNotBlank()) context.copyToClipboard(title, title) },
|
artist = artist,
|
||||||
onClick = { if (title.isNotBlank()) doSearch(title, true) },
|
status = status,
|
||||||
),
|
sourceName = sourceName,
|
||||||
|
isStubSource = isStubSource,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
|
||||||
Text(
|
|
||||||
text = author?.takeIf { it.isNotBlank() } ?: stringResource(R.string.unknown_studio),
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
modifier = Modifier
|
|
||||||
.secondaryItemAlpha()
|
|
||||||
.padding(top = 2.dp)
|
|
||||||
.clickableNoIndication(
|
|
||||||
onLongClick = {
|
|
||||||
if (!author.isNullOrBlank()) {
|
|
||||||
context.copyToClipboard(
|
|
||||||
author,
|
|
||||||
author,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick = { if (!author.isNullOrBlank()) doSearch(author, true) },
|
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
)
|
|
||||||
if (!artist.isNullOrBlank() && author != artist) {
|
|
||||||
Text(
|
|
||||||
text = artist,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
modifier = Modifier
|
|
||||||
.secondaryItemAlpha()
|
|
||||||
.padding(top = 2.dp)
|
|
||||||
.clickableNoIndication(
|
|
||||||
onLongClick = { context.copyToClipboard(artist, artist) },
|
|
||||||
onClick = { doSearch(artist, true) },
|
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = when (status) {
|
|
||||||
SAnime.ONGOING.toLong() -> Icons.Outlined.Schedule
|
|
||||||
SAnime.COMPLETED.toLong() -> Icons.Outlined.DoneAll
|
|
||||||
SAnime.LICENSED.toLong() -> Icons.Outlined.AttachMoney
|
|
||||||
SAnime.PUBLISHING_FINISHED.toLong() -> Icons.Outlined.Done
|
|
||||||
SAnime.CANCELLED.toLong() -> Icons.Outlined.Close
|
|
||||||
SAnime.ON_HIATUS.toLong() -> Icons.Outlined.Pause
|
|
||||||
else -> Icons.Outlined.Block
|
|
||||||
},
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 4.dp)
|
|
||||||
.size(16.dp),
|
|
||||||
)
|
|
||||||
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
|
|
||||||
Text(
|
|
||||||
text = when (status) {
|
|
||||||
SAnime.ONGOING.toLong() -> stringResource(R.string.ongoing)
|
|
||||||
SAnime.COMPLETED.toLong() -> stringResource(R.string.completed)
|
|
||||||
SAnime.LICENSED.toLong() -> stringResource(R.string.licensed)
|
|
||||||
SAnime.PUBLISHING_FINISHED.toLong() -> stringResource(
|
|
||||||
R.string.publishing_finished,
|
|
||||||
)
|
|
||||||
SAnime.CANCELLED.toLong() -> stringResource(R.string.cancelled)
|
|
||||||
SAnime.ON_HIATUS.toLong() -> stringResource(R.string.on_hiatus)
|
|
||||||
else -> stringResource(R.string.unknown)
|
|
||||||
},
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
DotSeparatorText()
|
|
||||||
if (isStubSource) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.Warning,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 4.dp)
|
|
||||||
.size(16.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.error,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = sourceName,
|
|
||||||
modifier = Modifier.clickableNoIndication { doSearch(sourceName, false) },
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,7 +373,6 @@ private fun AnimeAndSourceTitlesSmall(
|
||||||
coverDataProvider: () -> Anime,
|
coverDataProvider: () -> Anime,
|
||||||
onCoverClick: () -> Unit,
|
onCoverClick: () -> Unit,
|
||||||
title: String,
|
title: String,
|
||||||
context: Context,
|
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
author: String?,
|
author: String?,
|
||||||
artist: String?,
|
artist: String?,
|
||||||
|
@ -489,6 +398,31 @@ private fun AnimeAndSourceTitlesSmall(
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
) {
|
) {
|
||||||
|
AnimeContentInfo(
|
||||||
|
title = title,
|
||||||
|
doSearch = doSearch,
|
||||||
|
author = author,
|
||||||
|
artist = artist,
|
||||||
|
status = status,
|
||||||
|
sourceName = sourceName,
|
||||||
|
isStubSource = isStubSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AnimeContentInfo(
|
||||||
|
title: String,
|
||||||
|
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||||
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
|
author: String?,
|
||||||
|
artist: String?,
|
||||||
|
status: Long,
|
||||||
|
sourceName: String,
|
||||||
|
isStubSource: Boolean,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
Text(
|
Text(
|
||||||
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
@ -503,6 +437,7 @@ private fun AnimeAndSourceTitlesSmall(
|
||||||
},
|
},
|
||||||
onClick = { if (title.isNotBlank()) doSearch(title, true) },
|
onClick = { if (title.isNotBlank()) doSearch(title, true) },
|
||||||
),
|
),
|
||||||
|
textAlign = textAlign,
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
@ -533,6 +468,7 @@ private fun AnimeAndSourceTitlesSmall(
|
||||||
},
|
},
|
||||||
onClick = { if (!author.isNullOrBlank()) doSearch(author, true) },
|
onClick = { if (!author.isNullOrBlank()) doSearch(author, true) },
|
||||||
),
|
),
|
||||||
|
textAlign = textAlign,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,6 +491,7 @@ private fun AnimeAndSourceTitlesSmall(
|
||||||
onLongClick = { context.copyToClipboard(artist, artist) },
|
onLongClick = { context.copyToClipboard(artist, artist) },
|
||||||
onClick = { doSearch(artist, true) },
|
onClick = { doSearch(artist, true) },
|
||||||
),
|
),
|
||||||
|
textAlign = textAlign,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -586,9 +523,7 @@ private fun AnimeAndSourceTitlesSmall(
|
||||||
SAnime.ONGOING.toLong() -> stringResource(R.string.ongoing)
|
SAnime.ONGOING.toLong() -> stringResource(R.string.ongoing)
|
||||||
SAnime.COMPLETED.toLong() -> stringResource(R.string.completed)
|
SAnime.COMPLETED.toLong() -> stringResource(R.string.completed)
|
||||||
SAnime.LICENSED.toLong() -> stringResource(R.string.licensed)
|
SAnime.LICENSED.toLong() -> stringResource(R.string.licensed)
|
||||||
SAnime.PUBLISHING_FINISHED.toLong() -> stringResource(
|
SAnime.PUBLISHING_FINISHED.toLong() -> stringResource(R.string.publishing_finished)
|
||||||
R.string.publishing_finished,
|
|
||||||
)
|
|
||||||
SAnime.CANCELLED.toLong() -> stringResource(R.string.cancelled)
|
SAnime.CANCELLED.toLong() -> stringResource(R.string.cancelled)
|
||||||
SAnime.ON_HIATUS.toLong() -> stringResource(R.string.on_hiatus)
|
SAnime.ON_HIATUS.toLong() -> stringResource(R.string.on_hiatus)
|
||||||
else -> stringResource(R.string.unknown)
|
else -> stringResource(R.string.unknown)
|
||||||
|
@ -599,7 +534,7 @@ private fun AnimeAndSourceTitlesSmall(
|
||||||
DotSeparatorText()
|
DotSeparatorText()
|
||||||
if (isStubSource) {
|
if (isStubSource) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Warning,
|
imageVector = Icons.Filled.Warning,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 4.dp)
|
.padding(end = 4.dp)
|
||||||
|
@ -621,8 +556,6 @@ private fun AnimeAndSourceTitlesSmall(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AnimeSummary(
|
private fun AnimeSummary(
|
||||||
|
|
|
@ -5,9 +5,11 @@ import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
@ -26,7 +28,9 @@ import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
@ -44,6 +48,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.util.fastAll
|
import androidx.compose.ui.util.fastAll
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
|
@ -67,7 +72,7 @@ import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
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.ChapterList
|
||||||
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
|
||||||
|
@ -81,8 +86,10 @@ import tachiyomi.presentation.core.components.VerticalFastScroller
|
||||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
import tachiyomi.presentation.core.util.isScrollingUp
|
import tachiyomi.presentation.core.util.isScrollingUp
|
||||||
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
@ -98,7 +105,7 @@ fun MangaScreen(
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
|
@ -129,10 +136,10 @@ fun MangaScreen(
|
||||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||||
|
|
||||||
// For chapter swipe
|
// For chapter swipe
|
||||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Chapter selection
|
// Chapter selection
|
||||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onAllChapterSelected: (Boolean) -> Unit,
|
onAllChapterSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -238,7 +245,7 @@ private fun MangaScreenSmallImpl(
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
|
@ -271,16 +278,17 @@ private fun MangaScreenSmallImpl(
|
||||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||||
|
|
||||||
// For chapter swipe
|
// For chapter swipe
|
||||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Chapter selection
|
// Chapter selection
|
||||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onAllChapterSelected: (Boolean) -> Unit,
|
onAllChapterSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val chapterListState = rememberLazyListState()
|
val chapterListState = rememberLazyListState()
|
||||||
|
|
||||||
val chapters = remember(state) { state.processedChapters }
|
val chapters = remember(state) { state.processedChapters }
|
||||||
|
val listItem = remember(state) { state.chapterListItems }
|
||||||
|
|
||||||
val isAnySelected by remember {
|
val isAnySelected by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
|
@ -465,7 +473,8 @@ private fun MangaScreenSmallImpl(
|
||||||
|
|
||||||
sharedChapterItems(
|
sharedChapterItems(
|
||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
chapters = chapters,
|
chapters = listItem,
|
||||||
|
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||||
dateRelativeTime = dateRelativeTime,
|
dateRelativeTime = dateRelativeTime,
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
|
@ -492,7 +501,7 @@ fun MangaScreenLargeImpl(
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
|
@ -525,10 +534,10 @@ fun MangaScreenLargeImpl(
|
||||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||||
|
|
||||||
// For swipe actions
|
// For swipe actions
|
||||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Chapter selection
|
// Chapter selection
|
||||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onAllChapterSelected: (Boolean) -> Unit,
|
onAllChapterSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -536,6 +545,7 @@ fun MangaScreenLargeImpl(
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
val chapters = remember(state) { state.processedChapters }
|
val chapters = remember(state) { state.processedChapters }
|
||||||
|
val listItem = remember(state) { state.chapterListItems }
|
||||||
|
|
||||||
val isAnySelected by remember {
|
val isAnySelected by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
|
@ -716,7 +726,8 @@ fun MangaScreenLargeImpl(
|
||||||
|
|
||||||
sharedChapterItems(
|
sharedChapterItems(
|
||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
chapters = chapters,
|
chapters = listItem,
|
||||||
|
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||||
dateRelativeTime = dateRelativeTime,
|
dateRelativeTime = dateRelativeTime,
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
|
@ -736,12 +747,12 @@ fun MangaScreenLargeImpl(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SharedMangaBottomActionMenu(
|
private fun SharedMangaBottomActionMenu(
|
||||||
selected: List<ChapterItem>,
|
selected: List<ChapterList.Item>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
|
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
|
||||||
onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
|
onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
|
||||||
onMarkPreviousAsReadClicked: (Chapter) -> Unit,
|
onMarkPreviousAsReadClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||||
fillFraction: Float,
|
fillFraction: Float,
|
||||||
) {
|
) {
|
||||||
|
@ -779,34 +790,63 @@ private fun SharedMangaBottomActionMenu(
|
||||||
|
|
||||||
private fun LazyListScope.sharedChapterItems(
|
private fun LazyListScope.sharedChapterItems(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
chapters: List<ChapterItem>,
|
chapters: List<ChapterList>,
|
||||||
|
isAnyChapterSelected: Boolean,
|
||||||
dateRelativeTime: Boolean,
|
dateRelativeTime: Boolean,
|
||||||
dateFormat: DateFormat,
|
dateFormat: DateFormat,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = chapters,
|
items = chapters,
|
||||||
key = { "chapter-${it.chapter.id}" },
|
key = { item ->
|
||||||
|
when (item) {
|
||||||
|
is ChapterList.MissingCount -> "missing-count-${item.id}"
|
||||||
|
is ChapterList.Item -> "chapter-${item.id}"
|
||||||
|
}
|
||||||
|
},
|
||||||
contentType = { EntryScreenItem.ITEM },
|
contentType = { EntryScreenItem.ITEM },
|
||||||
) { chapterItem ->
|
) { item ->
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
when (item) {
|
||||||
|
is ChapterList.MissingCount -> {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
text = pluralStringResource(
|
||||||
|
id = R.plurals.missing_items,
|
||||||
|
count = item.count,
|
||||||
|
item.count,
|
||||||
|
),
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
)
|
||||||
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ChapterList.Item -> {
|
||||||
MangaChapterListItem(
|
MangaChapterListItem(
|
||||||
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.display_mode_chapter,
|
R.string.display_mode_chapter,
|
||||||
formatChapterNumber(chapterItem.chapter.chapterNumber),
|
formatChapterNumber(item.chapter.chapterNumber),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
chapterItem.chapter.name
|
item.chapter.name
|
||||||
},
|
},
|
||||||
date = chapterItem.chapter.dateUpload
|
date = item.chapter.dateUpload
|
||||||
.takeIf { it > 0L }
|
.takeIf { it > 0L }
|
||||||
?.let {
|
?.let {
|
||||||
Date(it).toRelativeString(
|
Date(it).toRelativeString(
|
||||||
|
@ -815,63 +855,58 @@ private fun LazyListScope.sharedChapterItems(
|
||||||
dateFormat,
|
dateFormat,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
readProgress = chapterItem.chapter.lastPageRead
|
readProgress = item.chapter.lastPageRead
|
||||||
.takeIf { !chapterItem.chapter.read && it > 0L }
|
.takeIf { !item.chapter.read && it > 0L }
|
||||||
?.let {
|
?.let {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.chapter_progress,
|
R.string.chapter_progress,
|
||||||
it + 1,
|
it + 1,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scanlator = chapterItem.chapter.scanlator.takeIf { !it.isNullOrBlank() },
|
scanlator = item.chapter.scanlator.takeIf { !it.isNullOrBlank() },
|
||||||
read = chapterItem.chapter.read,
|
read = item.chapter.read,
|
||||||
bookmark = chapterItem.chapter.bookmark,
|
bookmark = item.chapter.bookmark,
|
||||||
selected = chapterItem.selected,
|
selected = item.selected,
|
||||||
downloadIndicatorEnabled = chapters.fastAll { !it.selected },
|
downloadIndicatorEnabled = !isAnyChapterSelected,
|
||||||
downloadStateProvider = { chapterItem.downloadState },
|
downloadStateProvider = { item.downloadState },
|
||||||
downloadProgressProvider = { chapterItem.downloadProgress },
|
downloadProgressProvider = { item.downloadProgress },
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
onChapterSelected(chapterItem, !chapterItem.selected, true, true)
|
onChapterSelected(item, !item.selected, true, true)
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
onChapterItemClick(
|
onChapterItemClick(
|
||||||
chapterItem = chapterItem,
|
chapterItem = item,
|
||||||
chapters = chapters,
|
isAnyChapterSelected = isAnyChapterSelected,
|
||||||
onToggleSelection = {
|
onToggleSelection = { onChapterSelected(item, !item.selected, true, false) },
|
||||||
onChapterSelected(
|
|
||||||
chapterItem,
|
|
||||||
!chapterItem.selected,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onDownloadClick = if (onDownloadChapter != null) {
|
onDownloadClick = if (onDownloadChapter != null) {
|
||||||
{ onDownloadChapter(listOf(chapterItem), it) }
|
{ onDownloadChapter(listOf(item), it) }
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
},
|
},
|
||||||
onChapterSwipe = {
|
onChapterSwipe = {
|
||||||
onChapterSwipe(chapterItem, it)
|
onChapterSwipe(item, it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onChapterItemClick(
|
private fun onChapterItemClick(
|
||||||
chapterItem: ChapterItem,
|
chapterItem: ChapterList.Item,
|
||||||
chapters: List<ChapterItem>,
|
isAnyChapterSelected: Boolean,
|
||||||
onToggleSelection: (Boolean) -> Unit,
|
onToggleSelection: (Boolean) -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
chapterItem.selected -> onToggleSelection(false)
|
chapterItem.selected -> onToggleSelection(false)
|
||||||
chapters.fastAny { it.selected } -> onToggleSelection(true)
|
isAnyChapterSelected -> onToggleSelection(true)
|
||||||
else -> onChapterClicked(chapterItem.chapter)
|
else -> onChapterClicked(chapterItem.chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.presentation.entries.manga.components
|
package eu.kanade.presentation.entries.manga.components
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
@ -43,6 +42,7 @@ import androidx.compose.material.icons.outlined.Sync
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProvideTextStyle
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
import androidx.compose.material3.SuggestionChip
|
import androidx.compose.material3.SuggestionChip
|
||||||
|
@ -136,7 +136,6 @@ fun MangaInfoBox(
|
||||||
coverDataProvider = coverDataProvider,
|
coverDataProvider = coverDataProvider,
|
||||||
onCoverClick = onCoverClick,
|
onCoverClick = onCoverClick,
|
||||||
title = title,
|
title = title,
|
||||||
context = LocalContext.current,
|
|
||||||
doSearch = doSearch,
|
doSearch = doSearch,
|
||||||
author = author,
|
author = author,
|
||||||
artist = artist,
|
artist = artist,
|
||||||
|
@ -150,7 +149,6 @@ fun MangaInfoBox(
|
||||||
coverDataProvider = coverDataProvider,
|
coverDataProvider = coverDataProvider,
|
||||||
onCoverClick = onCoverClick,
|
onCoverClick = onCoverClick,
|
||||||
title = title,
|
title = title,
|
||||||
context = LocalContext.current,
|
|
||||||
doSearch = doSearch,
|
doSearch = doSearch,
|
||||||
author = author,
|
author = author,
|
||||||
artist = artist,
|
artist = artist,
|
||||||
|
@ -335,7 +333,6 @@ private fun MangaAndSourceTitlesLarge(
|
||||||
coverDataProvider: () -> Manga,
|
coverDataProvider: () -> Manga,
|
||||||
onCoverClick: () -> Unit,
|
onCoverClick: () -> Unit,
|
||||||
title: String,
|
title: String,
|
||||||
context: Context,
|
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
author: String?,
|
author: String?,
|
||||||
artist: String?,
|
artist: String?,
|
||||||
|
@ -356,104 +353,16 @@ private fun MangaAndSourceTitlesLarge(
|
||||||
onClick = onCoverClick,
|
onClick = onCoverClick,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(
|
MangaContentInfo(
|
||||||
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
title = title,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
doSearch = doSearch,
|
||||||
modifier = Modifier.clickableNoIndication(
|
author = author,
|
||||||
onLongClick = { if (title.isNotBlank()) context.copyToClipboard(title, title) },
|
artist = artist,
|
||||||
onClick = { if (title.isNotBlank()) doSearch(title, true) },
|
status = status,
|
||||||
),
|
sourceName = sourceName,
|
||||||
|
isStubSource = isStubSource,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
|
||||||
Text(
|
|
||||||
text = author?.takeIf { it.isNotBlank() } ?: stringResource(R.string.unknown_author),
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
modifier = Modifier
|
|
||||||
.secondaryItemAlpha()
|
|
||||||
.padding(top = 2.dp)
|
|
||||||
.clickableNoIndication(
|
|
||||||
onLongClick = {
|
|
||||||
if (!author.isNullOrBlank()) {
|
|
||||||
context.copyToClipboard(
|
|
||||||
author,
|
|
||||||
author,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick = { if (!author.isNullOrBlank()) doSearch(author, true) },
|
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
)
|
|
||||||
if (!artist.isNullOrBlank() && author != artist) {
|
|
||||||
Text(
|
|
||||||
text = artist,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
modifier = Modifier
|
|
||||||
.secondaryItemAlpha()
|
|
||||||
.padding(top = 2.dp)
|
|
||||||
.clickableNoIndication(
|
|
||||||
onLongClick = { context.copyToClipboard(artist, artist) },
|
|
||||||
onClick = { doSearch(artist, true) },
|
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = when (status) {
|
|
||||||
SManga.ONGOING.toLong() -> Icons.Outlined.Schedule
|
|
||||||
SManga.COMPLETED.toLong() -> Icons.Outlined.DoneAll
|
|
||||||
SManga.LICENSED.toLong() -> Icons.Outlined.AttachMoney
|
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> Icons.Outlined.Done
|
|
||||||
SManga.CANCELLED.toLong() -> Icons.Outlined.Close
|
|
||||||
SManga.ON_HIATUS.toLong() -> Icons.Outlined.Pause
|
|
||||||
else -> Icons.Outlined.Block
|
|
||||||
},
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 4.dp)
|
|
||||||
.size(16.dp),
|
|
||||||
)
|
|
||||||
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
|
|
||||||
Text(
|
|
||||||
text = when (status) {
|
|
||||||
SManga.ONGOING.toLong() -> stringResource(R.string.ongoing)
|
|
||||||
SManga.COMPLETED.toLong() -> stringResource(R.string.completed)
|
|
||||||
SManga.LICENSED.toLong() -> stringResource(R.string.licensed)
|
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(
|
|
||||||
R.string.publishing_finished,
|
|
||||||
)
|
|
||||||
SManga.CANCELLED.toLong() -> stringResource(R.string.cancelled)
|
|
||||||
SManga.ON_HIATUS.toLong() -> stringResource(R.string.on_hiatus)
|
|
||||||
else -> stringResource(R.string.unknown)
|
|
||||||
},
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
DotSeparatorText()
|
|
||||||
if (isStubSource) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Warning,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 4.dp)
|
|
||||||
.size(16.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.error,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = sourceName,
|
|
||||||
modifier = Modifier.clickableNoIndication { doSearch(sourceName, false) },
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,7 +372,6 @@ private fun MangaAndSourceTitlesSmall(
|
||||||
coverDataProvider: () -> Manga,
|
coverDataProvider: () -> Manga,
|
||||||
onCoverClick: () -> Unit,
|
onCoverClick: () -> Unit,
|
||||||
title: String,
|
title: String,
|
||||||
context: Context,
|
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
author: String?,
|
author: String?,
|
||||||
artist: String?,
|
artist: String?,
|
||||||
|
@ -489,6 +397,31 @@ private fun MangaAndSourceTitlesSmall(
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
) {
|
) {
|
||||||
|
MangaContentInfo(
|
||||||
|
title = title,
|
||||||
|
doSearch = doSearch,
|
||||||
|
author = author,
|
||||||
|
artist = artist,
|
||||||
|
status = status,
|
||||||
|
sourceName = sourceName,
|
||||||
|
isStubSource = isStubSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MangaContentInfo(
|
||||||
|
title: String,
|
||||||
|
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||||
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
|
author: String?,
|
||||||
|
artist: String?,
|
||||||
|
status: Long,
|
||||||
|
sourceName: String,
|
||||||
|
isStubSource: Boolean,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
Text(
|
Text(
|
||||||
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
@ -503,9 +436,11 @@ private fun MangaAndSourceTitlesSmall(
|
||||||
},
|
},
|
||||||
onClick = { if (title.isNotBlank()) doSearch(title, true) },
|
onClick = { if (title.isNotBlank()) doSearch(title, true) },
|
||||||
),
|
),
|
||||||
|
textAlign = textAlign,
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
@ -532,6 +467,7 @@ private fun MangaAndSourceTitlesSmall(
|
||||||
},
|
},
|
||||||
onClick = { if (!author.isNullOrBlank()) doSearch(author, true) },
|
onClick = { if (!author.isNullOrBlank()) doSearch(author, true) },
|
||||||
),
|
),
|
||||||
|
textAlign = textAlign,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,6 +490,7 @@ private fun MangaAndSourceTitlesSmall(
|
||||||
onLongClick = { context.copyToClipboard(artist, artist) },
|
onLongClick = { context.copyToClipboard(artist, artist) },
|
||||||
onClick = { doSearch(artist, true) },
|
onClick = { doSearch(artist, true) },
|
||||||
),
|
),
|
||||||
|
textAlign = textAlign,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -585,9 +522,7 @@ private fun MangaAndSourceTitlesSmall(
|
||||||
SManga.ONGOING.toLong() -> stringResource(R.string.ongoing)
|
SManga.ONGOING.toLong() -> stringResource(R.string.ongoing)
|
||||||
SManga.COMPLETED.toLong() -> stringResource(R.string.completed)
|
SManga.COMPLETED.toLong() -> stringResource(R.string.completed)
|
||||||
SManga.LICENSED.toLong() -> stringResource(R.string.licensed)
|
SManga.LICENSED.toLong() -> stringResource(R.string.licensed)
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(
|
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(R.string.publishing_finished)
|
||||||
R.string.publishing_finished,
|
|
||||||
)
|
|
||||||
SManga.CANCELLED.toLong() -> stringResource(R.string.cancelled)
|
SManga.CANCELLED.toLong() -> stringResource(R.string.cancelled)
|
||||||
SManga.ON_HIATUS.toLong() -> stringResource(R.string.on_hiatus)
|
SManga.ON_HIATUS.toLong() -> stringResource(R.string.on_hiatus)
|
||||||
else -> stringResource(R.string.unknown)
|
else -> stringResource(R.string.unknown)
|
||||||
|
@ -620,8 +555,6 @@ private fun MangaAndSourceTitlesSmall(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MangaSummary(
|
private fun MangaSummary(
|
||||||
|
|
|
@ -4,12 +4,15 @@ import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
|
import android.text.format.Formatter
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
@ -32,6 +35,8 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
||||||
|
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
||||||
import eu.kanade.presentation.permissions.PermissionRequestHelper
|
import eu.kanade.presentation.permissions.PermissionRequestHelper
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst
|
import eu.kanade.tachiyomi.data.backup.BackupConst
|
||||||
|
@ -41,6 +46,7 @@ import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.cache.EpisodeCache
|
import eu.kanade.tachiyomi.data.cache.EpisodeCache
|
||||||
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
@ -403,19 +409,22 @@ object SettingsDataScreen : SearchableSettings {
|
||||||
|
|
||||||
val chapterCache = remember { Injekt.get<ChapterCache>() }
|
val chapterCache = remember { Injekt.get<ChapterCache>() }
|
||||||
val episodeCache = remember { Injekt.get<EpisodeCache>() }
|
val episodeCache = remember { Injekt.get<EpisodeCache>() }
|
||||||
var readableSizeSema by remember { mutableIntStateOf(0) }
|
var cacheReadableSizeSema by remember { mutableIntStateOf(0) }
|
||||||
val readableSize = remember(readableSizeSema) { chapterCache.readableSize }
|
val cacheReadableMangaSize = remember(cacheReadableSizeSema) { chapterCache.readableSize }
|
||||||
val readableAnimeSize = remember(readableSizeSema) { episodeCache.readableSize }
|
val cacheReadableAnimeSize = remember(cacheReadableSizeSema) { episodeCache.readableSize }
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(R.string.label_data),
|
title = stringResource(R.string.label_data),
|
||||||
preferenceItems = listOf(
|
preferenceItems = listOf(
|
||||||
|
getMangaStorageInfoPref(cacheReadableMangaSize),
|
||||||
|
getAnimeStorageInfoPref(cacheReadableAnimeSize),
|
||||||
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.pref_clear_chapter_cache),
|
title = stringResource(R.string.pref_clear_chapter_cache),
|
||||||
subtitle = stringResource(
|
subtitle = stringResource(
|
||||||
R.string.used_cache_both,
|
R.string.used_cache_both,
|
||||||
readableAnimeSize,
|
cacheReadableAnimeSize,
|
||||||
readableSize,
|
cacheReadableMangaSize,
|
||||||
),
|
),
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launchNonCancellable {
|
scope.launchNonCancellable {
|
||||||
|
@ -423,7 +432,7 @@ object SettingsDataScreen : SearchableSettings {
|
||||||
val deletedFiles = chapterCache.clear() + episodeCache.clear()
|
val deletedFiles = chapterCache.clear() + episodeCache.clear()
|
||||||
withUIContext {
|
withUIContext {
|
||||||
context.toast(context.getString(R.string.cache_deleted, deletedFiles))
|
context.toast(context.getString(R.string.cache_deleted, deletedFiles))
|
||||||
readableSizeSema++
|
cacheReadableSizeSema++
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
logcat(LogPriority.ERROR, e)
|
logcat(LogPriority.ERROR, e)
|
||||||
|
@ -460,6 +469,60 @@ object SettingsDataScreen : SearchableSettings {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getMangaStorageInfoPref(
|
||||||
|
chapterCacheReadableSize: String,
|
||||||
|
): Preference.PreferenceItem.CustomPreference {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val available = remember {
|
||||||
|
Formatter.formatFileSize(context, DiskUtil.getAvailableStorageSpace(Environment.getDataDirectory()))
|
||||||
|
}
|
||||||
|
val total = remember {
|
||||||
|
Formatter.formatFileSize(context, DiskUtil.getTotalStorageSpace(Environment.getDataDirectory()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Preference.PreferenceItem.CustomPreference(
|
||||||
|
title = stringResource(R.string.pref_manga_storage_usage),
|
||||||
|
) {
|
||||||
|
BasePreferenceWidget(
|
||||||
|
title = stringResource(R.string.pref_manga_storage_usage),
|
||||||
|
subcomponent = {
|
||||||
|
// TODO: downloads, SD cards, bar representation?, i18n
|
||||||
|
Box(modifier = Modifier.padding(horizontal = PrefsHorizontalPadding)) {
|
||||||
|
Text(text = "Available: $available / $total (chapter cache: $chapterCacheReadableSize)")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getAnimeStorageInfoPref(
|
||||||
|
episodeCacheReadableSize: String,
|
||||||
|
): Preference.PreferenceItem.CustomPreference {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val available = remember {
|
||||||
|
Formatter.formatFileSize(context, DiskUtil.getAvailableStorageSpace(Environment.getDataDirectory()))
|
||||||
|
}
|
||||||
|
val total = remember {
|
||||||
|
Formatter.formatFileSize(context, DiskUtil.getTotalStorageSpace(Environment.getDataDirectory()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Preference.PreferenceItem.CustomPreference(
|
||||||
|
title = stringResource(R.string.pref_anime_storage_usage),
|
||||||
|
) {
|
||||||
|
BasePreferenceWidget(
|
||||||
|
title = stringResource(R.string.pref_anime_storage_usage),
|
||||||
|
subcomponent = {
|
||||||
|
// TODO: downloads, SD cards, bar representation?, i18n
|
||||||
|
Box(modifier = Modifier.padding(horizontal = PrefsHorizontalPadding)) {
|
||||||
|
Text(text = "Available: $available / $total (Episode cache: $episodeCacheReadableSize)")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class MissingRestoreComponents(
|
private data class MissingRestoreComponents(
|
||||||
|
|
|
@ -12,8 +12,9 @@ import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.text.InlineTextContent
|
import androidx.compose.foundation.text.InlineTextContent
|
||||||
import androidx.compose.foundation.text.appendInlineContent
|
import androidx.compose.foundation.text.appendInlineContent
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material.icons.outlined.CheckCircle
|
||||||
import androidx.compose.material.icons.outlined.Info
|
import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material.icons.outlined.OfflinePin
|
|
||||||
import androidx.compose.material.icons.outlined.Warning
|
import androidx.compose.material.icons.outlined.Warning
|
||||||
import androidx.compose.material3.CardColors
|
import androidx.compose.material3.CardColors
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
|
@ -256,7 +257,7 @@ private fun ChapterText(
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.OfflinePin,
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
contentDescription = stringResource(R.string.label_downloaded),
|
contentDescription = stringResource(R.string.label_downloaded),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -247,7 +247,7 @@ fun SearchResultItem(
|
||||||
) {
|
) {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.CheckCircle,
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.align(Alignment.TopEnd),
|
modifier = Modifier.align(Alignment.TopEnd),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
|
|
@ -97,9 +97,8 @@ fun AnimeUpdateScreen(
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
if (lastUpdated > 0L) {
|
|
||||||
animeUpdatesLastUpdatedItem(lastUpdated)
|
animeUpdatesLastUpdatedItem(lastUpdated)
|
||||||
}
|
|
||||||
animeUpdatesUiItems(
|
animeUpdatesUiItems(
|
||||||
uiModels = state.getUiModel(context, relativeTime),
|
uiModels = state.getUiModel(context, relativeTime),
|
||||||
selectionMode = state.selectionMode,
|
selectionMode = state.selectionMode,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.presentation.updates.anime
|
package eu.kanade.presentation.updates.anime
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
@ -40,6 +39,7 @@ import eu.kanade.presentation.entries.DotSeparatorText
|
||||||
import eu.kanade.presentation.entries.ItemCover
|
import eu.kanade.presentation.entries.ItemCover
|
||||||
import eu.kanade.presentation.entries.anime.components.EpisodeDownloadAction
|
import eu.kanade.presentation.entries.anime.components.EpisodeDownloadAction
|
||||||
import eu.kanade.presentation.entries.anime.components.EpisodeDownloadIndicator
|
import eu.kanade.presentation.entries.anime.components.EpisodeDownloadIndicator
|
||||||
|
import eu.kanade.presentation.util.relativeTimeSpanString
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload
|
import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload
|
||||||
import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesItem
|
import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesItem
|
||||||
|
@ -48,24 +48,13 @@ import tachiyomi.presentation.core.components.ListGroupHeader
|
||||||
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
import java.util.Date
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.time.Duration.Companion.minutes
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun LazyListScope.animeUpdatesLastUpdatedItem(
|
fun LazyListScope.animeUpdatesLastUpdatedItem(
|
||||||
lastUpdated: Long,
|
lastUpdated: Long,
|
||||||
) {
|
) {
|
||||||
item(key = "animeUpdates-lastUpdated") {
|
item(key = "animeUpdates-lastUpdated") {
|
||||||
val time = remember(lastUpdated) {
|
|
||||||
val now = Date().time
|
|
||||||
if (now - lastUpdated < 1.minutes.inWholeMilliseconds) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
DateUtils.getRelativeTimeSpanString(lastUpdated, now, DateUtils.MINUTE_IN_MILLIS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItemPlacement()
|
.animateItemPlacement()
|
||||||
|
@ -75,14 +64,7 @@ fun LazyListScope.animeUpdatesLastUpdatedItem(
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (time.isNullOrEmpty()) {
|
text = stringResource(R.string.updates_last_update_info, relativeTimeSpanString(lastUpdated)),
|
||||||
stringResource(
|
|
||||||
R.string.updates_last_update_info,
|
|
||||||
stringResource(R.string.updates_last_update_info_just_now),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.updates_last_update_info, time)
|
|
||||||
},
|
|
||||||
fontStyle = FontStyle.Italic,
|
fontStyle = FontStyle.Italic,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,9 +93,7 @@ fun MangaUpdateScreen(
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
if (lastUpdated > 0L) {
|
|
||||||
mangaUpdatesLastUpdatedItem(lastUpdated)
|
mangaUpdatesLastUpdatedItem(lastUpdated)
|
||||||
}
|
|
||||||
|
|
||||||
mangaUpdatesUiItems(
|
mangaUpdatesUiItems(
|
||||||
uiModels = state.getUiModel(context, relativeTime),
|
uiModels = state.getUiModel(context, relativeTime),
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.presentation.updates.manga
|
package eu.kanade.presentation.updates.manga
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
@ -40,6 +39,7 @@ import eu.kanade.presentation.entries.DotSeparatorText
|
||||||
import eu.kanade.presentation.entries.ItemCover
|
import eu.kanade.presentation.entries.ItemCover
|
||||||
import eu.kanade.presentation.entries.manga.components.ChapterDownloadAction
|
import eu.kanade.presentation.entries.manga.components.ChapterDownloadAction
|
||||||
import eu.kanade.presentation.entries.manga.components.ChapterDownloadIndicator
|
import eu.kanade.presentation.entries.manga.components.ChapterDownloadIndicator
|
||||||
|
import eu.kanade.presentation.util.relativeTimeSpanString
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload
|
import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload
|
||||||
import eu.kanade.tachiyomi.ui.updates.manga.MangaUpdatesItem
|
import eu.kanade.tachiyomi.ui.updates.manga.MangaUpdatesItem
|
||||||
|
@ -48,23 +48,12 @@ import tachiyomi.presentation.core.components.ListGroupHeader
|
||||||
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
import java.util.Date
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun LazyListScope.mangaUpdatesLastUpdatedItem(
|
fun LazyListScope.mangaUpdatesLastUpdatedItem(
|
||||||
lastUpdated: Long,
|
lastUpdated: Long,
|
||||||
) {
|
) {
|
||||||
item(key = "mangaUpdates-lastUpdated") {
|
item(key = "mangaUpdates-lastUpdated") {
|
||||||
val time = remember(lastUpdated) {
|
|
||||||
val now = Date().time
|
|
||||||
if (now - lastUpdated < 1.minutes.inWholeMilliseconds) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
DateUtils.getRelativeTimeSpanString(lastUpdated, now, DateUtils.MINUTE_IN_MILLIS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItemPlacement()
|
.animateItemPlacement()
|
||||||
|
@ -74,14 +63,7 @@ fun LazyListScope.mangaUpdatesLastUpdatedItem(
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (time.isNullOrEmpty()) {
|
text = stringResource(R.string.updates_last_update_info, relativeTimeSpanString(lastUpdated)),
|
||||||
stringResource(
|
|
||||||
R.string.updates_last_update_info,
|
|
||||||
stringResource(R.string.updates_last_update_info_just_now),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.updates_last_update_info, time)
|
|
||||||
},
|
|
||||||
fontStyle = FontStyle.Italic,
|
fontStyle = FontStyle.Italic,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
package eu.kanade.presentation.util
|
package eu.kanade.presentation.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import java.util.Date
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
fun Duration.toDurationString(context: Context, fallback: String): String {
|
fun Duration.toDurationString(context: Context, fallback: String): String {
|
||||||
return toComponents { days, hours, minutes, seconds, _ ->
|
return toComponents { days, hours, minutes, seconds, _ ->
|
||||||
|
@ -22,3 +28,14 @@ fun Duration.toDurationString(context: Context, fallback: String): String {
|
||||||
}.joinToString(" ").ifBlank { fallback }
|
}.joinToString(" ").ifBlank { fallback }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
fun relativeTimeSpanString(epochMillis: Long): String {
|
||||||
|
val now = Date().time
|
||||||
|
return when {
|
||||||
|
epochMillis <= 0L -> stringResource(R.string.relative_time_span_never)
|
||||||
|
now - epochMillis < 1.minutes.inWholeMilliseconds -> stringResource(R.string.updates_last_update_info_just_now)
|
||||||
|
else -> DateUtils.getRelativeTimeSpanString(epochMillis, now, DateUtils.MINUTE_IN_MILLIS).toString()
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import eu.kanade.tachiyomi.util.system.workManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
import tachiyomi.core.preference.getAndSet
|
import tachiyomi.core.preference.getAndSet
|
||||||
|
@ -503,6 +504,30 @@ object Migrations {
|
||||||
uiPreferences.relativeTime().set(false)
|
uiPreferences.relativeTime().set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 107) {
|
||||||
|
replacePreferences(
|
||||||
|
preferenceStore = preferenceStore,
|
||||||
|
filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") },
|
||||||
|
newKey = { Preference.privateKey(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (oldVersion < 108) {
|
||||||
|
val prefsToReplace = listOf(
|
||||||
|
"pref_download_only",
|
||||||
|
"incognito_mode",
|
||||||
|
"last_catalogue_source",
|
||||||
|
"trusted_signatures",
|
||||||
|
"last_app_closed",
|
||||||
|
"library_update_last_timestamp",
|
||||||
|
"library_unseen_updates_count",
|
||||||
|
"last_used_category",
|
||||||
|
)
|
||||||
|
replacePreferences(
|
||||||
|
preferenceStore = preferenceStore,
|
||||||
|
filterPredicate = { it.key in prefsToReplace },
|
||||||
|
newKey = { Preference.appStateKey(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -510,3 +535,41 @@ object Migrations {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun replacePreferences(
|
||||||
|
preferenceStore: PreferenceStore,
|
||||||
|
filterPredicate: (Map.Entry<String, Any?>) -> Boolean,
|
||||||
|
newKey: (String) -> String,
|
||||||
|
) {
|
||||||
|
preferenceStore.getAll()
|
||||||
|
.filter(filterPredicate)
|
||||||
|
.forEach { (key, value) ->
|
||||||
|
when (value) {
|
||||||
|
is Int -> {
|
||||||
|
preferenceStore.getInt(newKey(key)).set(value)
|
||||||
|
preferenceStore.getInt(key).delete()
|
||||||
|
}
|
||||||
|
is Long -> {
|
||||||
|
preferenceStore.getLong(newKey(key)).set(value)
|
||||||
|
preferenceStore.getLong(key).delete()
|
||||||
|
}
|
||||||
|
is Float -> {
|
||||||
|
preferenceStore.getFloat(newKey(key)).set(value)
|
||||||
|
preferenceStore.getFloat(key).delete()
|
||||||
|
}
|
||||||
|
is String -> {
|
||||||
|
preferenceStore.getString(newKey(key)).set(value)
|
||||||
|
preferenceStore.getString(key).delete()
|
||||||
|
}
|
||||||
|
is Boolean -> {
|
||||||
|
preferenceStore.getBoolean(newKey(key)).set(value)
|
||||||
|
preferenceStore.getBoolean(key).delete()
|
||||||
|
}
|
||||||
|
is Set<*> -> (value as? Set<String>)?.let {
|
||||||
|
preferenceStore.getStringSet(newKey(key)).set(value)
|
||||||
|
preferenceStore.getStringSet(key).delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import logcat.LogPriority
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.gzip
|
import okio.gzip
|
||||||
import okio.sink
|
import okio.sink
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
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.data.handlers.manga.MangaDatabaseHandler
|
import tachiyomi.data.handlers.manga.MangaDatabaseHandler
|
||||||
|
@ -444,6 +445,6 @@ class BackupCreator(
|
||||||
}
|
}
|
||||||
backupPreferences.add(toAdd)
|
backupPreferences.add(toAdd)
|
||||||
}
|
}
|
||||||
return backupPreferences
|
return backupPreferences.filter { !Preference.isPrivate(it.key) && !Preference.isAppState(it.key) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@ import dataanime.Anime_sync
|
||||||
import dataanime.Animes
|
import dataanime.Animes
|
||||||
import eu.kanade.domain.entries.anime.interactor.UpdateAnime
|
import eu.kanade.domain.entries.anime.interactor.UpdateAnime
|
||||||
import eu.kanade.domain.entries.manga.interactor.UpdateManga
|
import eu.kanade.domain.entries.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.items.chapter.model.copyFrom
|
|
||||||
import eu.kanade.domain.items.episode.model.copyFrom
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupAnime
|
import eu.kanade.tachiyomi.data.backup.models.BackupAnime
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupAnimeHistory
|
import eu.kanade.tachiyomi.data.backup.models.BackupAnimeHistory
|
||||||
|
@ -51,7 +49,9 @@ import tachiyomi.domain.entries.manga.interactor.MangaFetchInterval
|
||||||
import tachiyomi.domain.entries.manga.model.Manga
|
import tachiyomi.domain.entries.manga.model.Manga
|
||||||
import tachiyomi.domain.history.anime.model.AnimeHistoryUpdate
|
import tachiyomi.domain.history.anime.model.AnimeHistoryUpdate
|
||||||
import tachiyomi.domain.history.manga.model.MangaHistoryUpdate
|
import tachiyomi.domain.history.manga.model.MangaHistoryUpdate
|
||||||
|
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
import tachiyomi.domain.items.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.track.anime.model.AnimeTrack
|
import tachiyomi.domain.track.anime.model.AnimeTrack
|
||||||
|
@ -73,11 +73,13 @@ class BackupRestorer(
|
||||||
private val mangaHandler: MangaDatabaseHandler = Injekt.get()
|
private val mangaHandler: MangaDatabaseHandler = Injekt.get()
|
||||||
private val updateManga: UpdateManga = Injekt.get()
|
private val updateManga: UpdateManga = Injekt.get()
|
||||||
private val getMangaCategories: GetMangaCategories = Injekt.get()
|
private val getMangaCategories: GetMangaCategories = Injekt.get()
|
||||||
|
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get()
|
||||||
private val mangaFetchInterval: MangaFetchInterval = Injekt.get()
|
private val mangaFetchInterval: MangaFetchInterval = Injekt.get()
|
||||||
|
|
||||||
private val animeHandler: AnimeDatabaseHandler = Injekt.get()
|
private val animeHandler: AnimeDatabaseHandler = Injekt.get()
|
||||||
private val updateAnime: UpdateAnime = Injekt.get()
|
private val updateAnime: UpdateAnime = Injekt.get()
|
||||||
private val getAnimeCategories: GetAnimeCategories = Injekt.get()
|
private val getAnimeCategories: GetAnimeCategories = Injekt.get()
|
||||||
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId = Injekt.get()
|
||||||
private val animeFetchInterval: AnimeFetchInterval = Injekt.get()
|
private val animeFetchInterval: AnimeFetchInterval = Injekt.get()
|
||||||
|
|
||||||
private val libraryPreferences: LibraryPreferences = Injekt.get()
|
private val libraryPreferences: LibraryPreferences = Injekt.get()
|
||||||
|
@ -423,33 +425,38 @@ class BackupRestorer(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
chapters: List<Chapter>,
|
chapters: List<Chapter>,
|
||||||
) {
|
) {
|
||||||
val dbChapters = mangaHandler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id!!) }
|
val dbChaptersByUrl = getChaptersByMangaId.await(manga.id)
|
||||||
|
.associateBy { it.url }
|
||||||
|
|
||||||
val processed = chapters.map { chapter ->
|
val processed = chapters.map { chapter ->
|
||||||
var updatedChapter = chapter
|
var updatedChapter = chapter
|
||||||
val dbChapter = dbChapters.find { it.url == updatedChapter.url }
|
|
||||||
|
val dbChapter = dbChaptersByUrl[updatedChapter.url]
|
||||||
if (dbChapter != null) {
|
if (dbChapter != null) {
|
||||||
updatedChapter = updatedChapter.copy(id = dbChapter._id)
|
updatedChapter = updatedChapter
|
||||||
updatedChapter = updatedChapter.copyFrom(dbChapter)
|
.copyFrom(dbChapter)
|
||||||
|
.copy(
|
||||||
|
id = dbChapter.id,
|
||||||
|
bookmark = updatedChapter.bookmark || dbChapter.bookmark,
|
||||||
|
)
|
||||||
if (dbChapter.read && !updatedChapter.read) {
|
if (dbChapter.read && !updatedChapter.read) {
|
||||||
updatedChapter = updatedChapter.copy(
|
updatedChapter = updatedChapter.copy(
|
||||||
read = true,
|
read = true,
|
||||||
lastPageRead = dbChapter.last_page_read,
|
lastPageRead = dbChapter.lastPageRead,
|
||||||
|
)
|
||||||
|
} else if (updatedChapter.lastPageRead == 0L && dbChapter.lastPageRead != 0L) {
|
||||||
|
updatedChapter = updatedChapter.copy(
|
||||||
|
lastPageRead = dbChapter.lastPageRead,
|
||||||
)
|
)
|
||||||
} else if (chapter.lastPageRead == 0L && dbChapter.last_page_read != 0L) {
|
|
||||||
updatedChapter = updatedChapter.copy(lastPageRead = dbChapter.last_page_read)
|
|
||||||
}
|
|
||||||
if (!updatedChapter.bookmark && dbChapter.bookmark) {
|
|
||||||
updatedChapter = updatedChapter.copy(bookmark = true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedChapter.copy(mangaId = manga.id ?: -1)
|
updatedChapter.copy(mangaId = manga.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
val newChapters = processed.groupBy { it.id > 0 }
|
val (existingChapters, newChapters) = processed.partition { it.id > 0 }
|
||||||
newChapters[true]?.let { updateKnownChapters(it) }
|
updateKnownChapters(existingChapters)
|
||||||
newChapters[false]?.let { insertChapters(it) }
|
insertChapters(newChapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -870,43 +877,46 @@ class BackupRestorer(
|
||||||
|
|
||||||
private suspend fun restoreEpisodes(
|
private suspend fun restoreEpisodes(
|
||||||
anime: Anime,
|
anime: Anime,
|
||||||
episodes: List<tachiyomi.domain.items.episode.model.Episode>,
|
episodes: List<Episode>,
|
||||||
) {
|
) {
|
||||||
val dbEpisodes = animeHandler.awaitList { episodesQueries.getEpisodesByAnimeId(anime.id!!) }
|
val dbEpisodesByUrl = getEpisodesByAnimeId.await(anime.id)
|
||||||
|
.associateBy { it.url }
|
||||||
|
|
||||||
val processed = episodes.map { episode ->
|
val processed = episodes.map { episode ->
|
||||||
var updatedEpisode = episode
|
var updatedEpisode = episode
|
||||||
val dbEpisode = dbEpisodes.find { it.url == updatedEpisode.url }
|
|
||||||
|
val dbEpisode = dbEpisodesByUrl[updatedEpisode.url]
|
||||||
if (dbEpisode != null) {
|
if (dbEpisode != null) {
|
||||||
updatedEpisode = updatedEpisode.copy(id = dbEpisode._id)
|
updatedEpisode = updatedEpisode
|
||||||
updatedEpisode = updatedEpisode.copyFrom(dbEpisode)
|
.copyFrom(dbEpisode)
|
||||||
|
.copy(
|
||||||
|
id = dbEpisode.id,
|
||||||
|
bookmark = updatedEpisode.bookmark || dbEpisode.bookmark,
|
||||||
|
)
|
||||||
if (dbEpisode.seen && !updatedEpisode.seen) {
|
if (dbEpisode.seen && !updatedEpisode.seen) {
|
||||||
updatedEpisode = updatedEpisode.copy(
|
updatedEpisode = updatedEpisode.copy(
|
||||||
seen = true,
|
seen = true,
|
||||||
lastSecondSeen = dbEpisode.last_second_seen,
|
lastSecondSeen = dbEpisode.lastSecondSeen,
|
||||||
)
|
)
|
||||||
} else if (updatedEpisode.lastSecondSeen == 0L && dbEpisode.last_second_seen != 0L) {
|
} else if (updatedEpisode.lastSecondSeen == 0L && dbEpisode.lastSecondSeen != 0L) {
|
||||||
updatedEpisode = updatedEpisode.copy(
|
updatedEpisode = updatedEpisode.copy(
|
||||||
lastSecondSeen = dbEpisode.last_second_seen,
|
lastSecondSeen = dbEpisode.lastSecondSeen,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!updatedEpisode.bookmark && dbEpisode.bookmark) {
|
|
||||||
updatedEpisode = updatedEpisode.copy(bookmark = true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedEpisode.copy(animeId = anime.id ?: -1)
|
updatedEpisode.copy(animeId = anime.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
val newEpisodes = processed.groupBy { it.id > 0 }
|
val (existingEpisodes, newEpisodes) = processed.partition { it.id > 0 }
|
||||||
newEpisodes[true]?.let { updateKnownEpisodes(it) }
|
updateKnownEpisodes(existingEpisodes)
|
||||||
newEpisodes[false]?.let { insertEpisodes(it) }
|
insertEpisodes(newEpisodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts list of episodes
|
* Inserts list of episodes
|
||||||
*/
|
*/
|
||||||
private suspend fun insertEpisodes(episodes: List<tachiyomi.domain.items.episode.model.Episode>) {
|
private suspend fun insertEpisodes(episodes: List<Episode>) {
|
||||||
animeHandler.await(true) {
|
animeHandler.await(true) {
|
||||||
episodes.forEach { episode ->
|
episodes.forEach { episode ->
|
||||||
episodesQueries.insert(
|
episodesQueries.insert(
|
||||||
|
@ -931,7 +941,7 @@ class BackupRestorer(
|
||||||
* Updates a list of episodes with known database ids
|
* Updates a list of episodes with known database ids
|
||||||
*/
|
*/
|
||||||
private suspend fun updateKnownEpisodes(
|
private suspend fun updateKnownEpisodes(
|
||||||
episodes: List<tachiyomi.domain.items.episode.model.Episode>,
|
episodes: List<Episode>,
|
||||||
) {
|
) {
|
||||||
animeHandler.await(true) {
|
animeHandler.await(true) {
|
||||||
episodes.forEach { episode ->
|
episodes.forEach { episode ->
|
||||||
|
|
|
@ -13,7 +13,7 @@ import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.core.util.lang.withUIContext
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.history.anime.interactor.GetAnimeHistory
|
import tachiyomi.domain.history.anime.interactor.GetAnimeHistory
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
@ -62,7 +62,7 @@ interface AnimeTracker {
|
||||||
item.anime_id = animeId
|
item.anime_id = animeId
|
||||||
try {
|
try {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
val allEpisodes = Injekt.get<GetEpisodeByAnimeId>().await(animeId)
|
val allEpisodes = Injekt.get<GetEpisodesByAnimeId>().await(animeId)
|
||||||
val hasSeenEpisodes = allEpisodes.any { it.seen }
|
val hasSeenEpisodes = allEpisodes.any { it.seen }
|
||||||
bind(item, hasSeenEpisodes)
|
bind(item, hasSeenEpisodes)
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.core.util.lang.withUIContext
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.history.manga.interactor.GetMangaHistory
|
import tachiyomi.domain.history.manga.interactor.GetMangaHistory
|
||||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
@ -62,7 +62,7 @@ interface MangaTracker {
|
||||||
item.manga_id = mangaId
|
item.manga_id = mangaId
|
||||||
try {
|
try {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
val allChapters = Injekt.get<GetChapterByMangaId>().await(mangaId)
|
val allChapters = Injekt.get<GetChaptersByMangaId>().await(mangaId)
|
||||||
val hasReadChapters = allChapters.any { it.read }
|
val hasReadChapters = allChapters.any { it.read }
|
||||||
bind(item, hasReadChapters)
|
bind(item, hasReadChapters)
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ import tachiyomi.domain.category.anime.interactor.GetAnimeCategories
|
||||||
import tachiyomi.domain.category.anime.interactor.SetAnimeCategories
|
import tachiyomi.domain.category.anime.interactor.SetAnimeCategories
|
||||||
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.items.episode.interactor.GetEpisodeByAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||||
import tachiyomi.domain.items.episode.model.toEpisodeUpdate
|
import tachiyomi.domain.items.episode.model.toEpisodeUpdate
|
||||||
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
||||||
|
@ -150,7 +150,7 @@ internal class MigrateAnimeDialogScreenModel(
|
||||||
private val sourceManager: AnimeSourceManager = Injekt.get(),
|
private val sourceManager: AnimeSourceManager = Injekt.get(),
|
||||||
private val downloadManager: AnimeDownloadManager = Injekt.get(),
|
private val downloadManager: AnimeDownloadManager = Injekt.get(),
|
||||||
private val updateAnime: UpdateAnime = Injekt.get(),
|
private val updateAnime: UpdateAnime = Injekt.get(),
|
||||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get(),
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId = Injekt.get(),
|
||||||
private val syncEpisodesWithSource: SyncEpisodesWithSource = Injekt.get(),
|
private val syncEpisodesWithSource: SyncEpisodesWithSource = Injekt.get(),
|
||||||
private val updateEpisode: UpdateEpisode = Injekt.get(),
|
private val updateEpisode: UpdateEpisode = Injekt.get(),
|
||||||
private val getCategories: GetAnimeCategories = Injekt.get(),
|
private val getCategories: GetAnimeCategories = Injekt.get(),
|
||||||
|
@ -222,8 +222,8 @@ internal class MigrateAnimeDialogScreenModel(
|
||||||
|
|
||||||
// Update chapters read, bookmark and dateFetch
|
// Update chapters read, bookmark and dateFetch
|
||||||
if (migrateEpisodes) {
|
if (migrateEpisodes) {
|
||||||
val prevAnimeEpisodes = getEpisodeByAnimeId.await(oldAnime.id)
|
val prevAnimeEpisodes = getEpisodesByAnimeId.await(oldAnime.id)
|
||||||
val animeEpisodes = getEpisodeByAnimeId.await(newAnime.id)
|
val animeEpisodes = getEpisodesByAnimeId.await(newAnime.id)
|
||||||
|
|
||||||
val maxEpisodeSeen = prevAnimeEpisodes
|
val maxEpisodeSeen = prevAnimeEpisodes
|
||||||
.filter { it.seen }
|
.filter { it.seen }
|
||||||
|
|
|
@ -43,7 +43,7 @@ import tachiyomi.domain.category.manga.interactor.GetMangaCategories
|
||||||
import tachiyomi.domain.category.manga.interactor.SetMangaCategories
|
import tachiyomi.domain.category.manga.interactor.SetMangaCategories
|
||||||
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.items.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.items.chapter.model.toChapterUpdate
|
import tachiyomi.domain.items.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.source.manga.service.MangaSourceManager
|
import tachiyomi.domain.source.manga.service.MangaSourceManager
|
||||||
|
@ -150,7 +150,7 @@ internal class MigrateMangaDialogScreenModel(
|
||||||
private val sourceManager: MangaSourceManager = Injekt.get(),
|
private val sourceManager: MangaSourceManager = Injekt.get(),
|
||||||
private val downloadManager: MangaDownloadManager = Injekt.get(),
|
private val downloadManager: MangaDownloadManager = Injekt.get(),
|
||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga = Injekt.get(),
|
||||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
|
||||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||||
private val getCategories: GetMangaCategories = Injekt.get(),
|
private val getCategories: GetMangaCategories = Injekt.get(),
|
||||||
|
@ -222,8 +222,8 @@ internal class MigrateMangaDialogScreenModel(
|
||||||
|
|
||||||
// Update chapters read, bookmark and dateFetch
|
// Update chapters read, bookmark and dateFetch
|
||||||
if (migrateChapters) {
|
if (migrateChapters) {
|
||||||
val prevMangaChapters = getChapterByMangaId.await(oldManga.id)
|
val prevMangaChapters = getChaptersByMangaId.await(oldManga.id)
|
||||||
val mangaChapters = getChapterByMangaId.await(newManga.id)
|
val mangaChapters = getChaptersByMangaId.await(newManga.id)
|
||||||
|
|
||||||
val maxChapterRead = prevMangaChapters
|
val maxChapterRead = prevMangaChapters
|
||||||
.filter { it.read }
|
.filter { it.read }
|
||||||
|
|
|
@ -10,6 +10,7 @@ import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.core.util.addOrRemove
|
import eu.kanade.core.util.addOrRemove
|
||||||
|
import eu.kanade.core.util.insertSeparators
|
||||||
import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags
|
import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags
|
||||||
import eu.kanade.domain.entries.anime.interactor.UpdateAnime
|
import eu.kanade.domain.entries.anime.interactor.UpdateAnime
|
||||||
import eu.kanade.domain.entries.anime.model.downloadedFilter
|
import eu.kanade.domain.entries.anime.model.downloadedFilter
|
||||||
|
@ -74,6 +75,7 @@ import tachiyomi.domain.items.episode.model.Episode
|
||||||
import tachiyomi.domain.items.episode.model.EpisodeUpdate
|
import tachiyomi.domain.items.episode.model.EpisodeUpdate
|
||||||
import tachiyomi.domain.items.episode.model.NoEpisodesException
|
import tachiyomi.domain.items.episode.model.NoEpisodesException
|
||||||
import tachiyomi.domain.items.episode.service.getEpisodeSort
|
import tachiyomi.domain.items.episode.service.getEpisodeSort
|
||||||
|
import tachiyomi.domain.items.service.calculateEpisodeGap
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
||||||
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
||||||
|
@ -81,6 +83,7 @@ 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.floor
|
||||||
|
|
||||||
class AnimeScreenModel(
|
class AnimeScreenModel(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
|
@ -125,7 +128,7 @@ class AnimeScreenModel(
|
||||||
private val isFavorited: Boolean
|
private val isFavorited: Boolean
|
||||||
get() = anime?.favorite ?: false
|
get() = anime?.favorite ?: false
|
||||||
|
|
||||||
private val processedEpisodes: List<EpisodeItem>?
|
private val processedEpisodes: List<EpisodeList.Item>?
|
||||||
get() = successState?.processedEpisodes
|
get() = successState?.processedEpisodes
|
||||||
|
|
||||||
val episodeSwipeStartAction = libraryPreferences.swipeEpisodeEndAction().get()
|
val episodeSwipeStartAction = libraryPreferences.swipeEpisodeEndAction().get()
|
||||||
|
@ -171,7 +174,7 @@ class AnimeScreenModel(
|
||||||
updateSuccessState {
|
updateSuccessState {
|
||||||
it.copy(
|
it.copy(
|
||||||
anime = anime,
|
anime = anime,
|
||||||
episodes = episodes.toEpisodeItems(anime),
|
episodes = episodes.toEpisodeListItems(anime),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +185,7 @@ class AnimeScreenModel(
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
val anime = getAnimeAndEpisodes.awaitAnime(animeId)
|
val anime = getAnimeAndEpisodes.awaitAnime(animeId)
|
||||||
val episodes = getAnimeAndEpisodes.awaitEpisodes(animeId)
|
val episodes = getAnimeAndEpisodes.awaitEpisodes(animeId)
|
||||||
.toEpisodeItems(anime)
|
.toEpisodeListItems(anime)
|
||||||
|
|
||||||
if (!anime.favorite) {
|
if (!anime.favorite) {
|
||||||
setAnimeDefaultEpisodeFlags.await(anime)
|
setAnimeDefaultEpisodeFlags.await(anime)
|
||||||
|
@ -477,7 +480,7 @@ class AnimeScreenModel(
|
||||||
|
|
||||||
private fun updateDownloadState(download: AnimeDownload) {
|
private fun updateDownloadState(download: AnimeDownload) {
|
||||||
updateSuccessState { successState ->
|
updateSuccessState { successState ->
|
||||||
val modifiedIndex = successState.episodes.indexOfFirst { it.episode.id == download.episode.id }
|
val modifiedIndex = successState.episodes.indexOfFirst { it.id == download.episode.id }
|
||||||
if (modifiedIndex < 0) return@updateSuccessState successState
|
if (modifiedIndex < 0) return@updateSuccessState successState
|
||||||
|
|
||||||
val newEpisodes = successState.episodes.toMutableList().apply {
|
val newEpisodes = successState.episodes.toMutableList().apply {
|
||||||
|
@ -489,7 +492,7 @@ class AnimeScreenModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Episode>.toEpisodeItems(anime: Anime): List<EpisodeItem> {
|
private fun List<Episode>.toEpisodeListItems(anime: Anime): List<EpisodeList.Item> {
|
||||||
val isLocal = anime.isLocal()
|
val isLocal = anime.isLocal()
|
||||||
return map { episode ->
|
return map { episode ->
|
||||||
val activeDownload = if (isLocal) {
|
val activeDownload = if (isLocal) {
|
||||||
|
@ -513,7 +516,7 @@ class AnimeScreenModel(
|
||||||
else -> AnimeDownload.State.NOT_DOWNLOADED
|
else -> AnimeDownload.State.NOT_DOWNLOADED
|
||||||
}
|
}
|
||||||
|
|
||||||
EpisodeItem(
|
EpisodeList.Item(
|
||||||
episode = episode,
|
episode = episode,
|
||||||
downloadState = downloadState,
|
downloadState = downloadState,
|
||||||
downloadProgress = activeDownload?.progress ?: 0,
|
downloadProgress = activeDownload?.progress ?: 0,
|
||||||
|
@ -561,7 +564,7 @@ class AnimeScreenModel(
|
||||||
/**
|
/**
|
||||||
* @throws IllegalStateException if the swipe action is [LibraryPreferences.EpisodeSwipeAction.Disabled]
|
* @throws IllegalStateException if the swipe action is [LibraryPreferences.EpisodeSwipeAction.Disabled]
|
||||||
*/
|
*/
|
||||||
fun episodeSwipe(episodeItem: EpisodeItem, swipeAction: LibraryPreferences.EpisodeSwipeAction) {
|
fun episodeSwipe(episodeItem: EpisodeList.Item, swipeAction: LibraryPreferences.EpisodeSwipeAction) {
|
||||||
screenModelScope.launch {
|
screenModelScope.launch {
|
||||||
executeEpisodeSwipeAction(episodeItem, swipeAction)
|
executeEpisodeSwipeAction(episodeItem, swipeAction)
|
||||||
}
|
}
|
||||||
|
@ -571,7 +574,7 @@ class AnimeScreenModel(
|
||||||
* @throws IllegalStateException if the swipe action is [LibraryPreferences.EpisodeSwipeAction.Disabled]
|
* @throws IllegalStateException if the swipe action is [LibraryPreferences.EpisodeSwipeAction.Disabled]
|
||||||
*/
|
*/
|
||||||
private fun executeEpisodeSwipeAction(
|
private fun executeEpisodeSwipeAction(
|
||||||
episodeItem: EpisodeItem,
|
episodeItem: EpisodeList.Item,
|
||||||
swipeAction: LibraryPreferences.EpisodeSwipeAction,
|
swipeAction: LibraryPreferences.EpisodeSwipeAction,
|
||||||
) {
|
) {
|
||||||
val episode = episodeItem.episode
|
val episode = episodeItem.episode
|
||||||
|
@ -654,7 +657,7 @@ class AnimeScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runEpisodeDownloadActions(
|
fun runEpisodeDownloadActions(
|
||||||
items: List<EpisodeItem>,
|
items: List<EpisodeList.Item>,
|
||||||
action: EpisodeDownloadAction,
|
action: EpisodeDownloadAction,
|
||||||
) {
|
) {
|
||||||
when (action) {
|
when (action) {
|
||||||
|
@ -669,7 +672,7 @@ class AnimeScreenModel(
|
||||||
startDownload(listOf(episode), true)
|
startDownload(listOf(episode), true)
|
||||||
}
|
}
|
||||||
EpisodeDownloadAction.CANCEL -> {
|
EpisodeDownloadAction.CANCEL -> {
|
||||||
val episodeId = items.singleOrNull()?.episode?.id ?: return
|
val episodeId = items.singleOrNull()?.id ?: return
|
||||||
cancelDownload(episodeId)
|
cancelDownload(episodeId)
|
||||||
}
|
}
|
||||||
EpisodeDownloadAction.DELETE -> {
|
EpisodeDownloadAction.DELETE -> {
|
||||||
|
@ -880,14 +883,14 @@ class AnimeScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleSelection(
|
fun toggleSelection(
|
||||||
item: EpisodeItem,
|
item: EpisodeList.Item,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
userSelected: Boolean = false,
|
userSelected: Boolean = false,
|
||||||
fromLongPress: Boolean = false,
|
fromLongPress: Boolean = false,
|
||||||
) {
|
) {
|
||||||
updateSuccessState { successState ->
|
updateSuccessState { successState ->
|
||||||
val newEpisodes = successState.processedEpisodes.toMutableList().apply {
|
val newEpisodes = successState.processedEpisodes.toMutableList().apply {
|
||||||
val selectedIndex = successState.processedEpisodes.indexOfFirst { it.episode.id == item.episode.id }
|
val selectedIndex = successState.processedEpisodes.indexOfFirst { it.id == item.episode.id }
|
||||||
if (selectedIndex < 0) return@apply
|
if (selectedIndex < 0) return@apply
|
||||||
|
|
||||||
val selectedItem = get(selectedIndex)
|
val selectedItem = get(selectedIndex)
|
||||||
|
@ -895,7 +898,7 @@ class AnimeScreenModel(
|
||||||
|
|
||||||
val firstSelection = none { it.selected }
|
val firstSelection = none { it.selected }
|
||||||
set(selectedIndex, selectedItem.copy(selected = selected))
|
set(selectedIndex, selectedItem.copy(selected = selected))
|
||||||
selectedEpisodeIds.addOrRemove(item.episode.id, selected)
|
selectedEpisodeIds.addOrRemove(item.id, selected)
|
||||||
|
|
||||||
if (selected && userSelected && fromLongPress) {
|
if (selected && userSelected && fromLongPress) {
|
||||||
if (firstSelection) {
|
if (firstSelection) {
|
||||||
|
@ -918,7 +921,7 @@ class AnimeScreenModel(
|
||||||
range.forEach {
|
range.forEach {
|
||||||
val inbetweenItem = get(it)
|
val inbetweenItem = get(it)
|
||||||
if (!inbetweenItem.selected) {
|
if (!inbetweenItem.selected) {
|
||||||
selectedEpisodeIds.add(inbetweenItem.episode.id)
|
selectedEpisodeIds.add(inbetweenItem.id)
|
||||||
set(it, inbetweenItem.copy(selected = true))
|
set(it, inbetweenItem.copy(selected = true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -946,7 +949,7 @@ class AnimeScreenModel(
|
||||||
fun toggleAllSelection(selected: Boolean) {
|
fun toggleAllSelection(selected: Boolean) {
|
||||||
updateSuccessState { successState ->
|
updateSuccessState { successState ->
|
||||||
val newEpisodes = successState.episodes.map {
|
val newEpisodes = successState.episodes.map {
|
||||||
selectedEpisodeIds.addOrRemove(it.episode.id, selected)
|
selectedEpisodeIds.addOrRemove(it.id, selected)
|
||||||
it.copy(selected = selected)
|
it.copy(selected = selected)
|
||||||
}
|
}
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
|
@ -958,7 +961,7 @@ class AnimeScreenModel(
|
||||||
fun invertSelection() {
|
fun invertSelection() {
|
||||||
updateSuccessState { successState ->
|
updateSuccessState { successState ->
|
||||||
val newEpisodes = successState.episodes.map {
|
val newEpisodes = successState.episodes.map {
|
||||||
selectedEpisodeIds.addOrRemove(it.episode.id, !it.selected)
|
selectedEpisodeIds.addOrRemove(it.id, !it.selected)
|
||||||
it.copy(selected = !it.selected)
|
it.copy(selected = !it.selected)
|
||||||
}
|
}
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
|
@ -1060,7 +1063,7 @@ class AnimeScreenModel(
|
||||||
val anime: Anime,
|
val anime: Anime,
|
||||||
val source: AnimeSource,
|
val source: AnimeSource,
|
||||||
val isFromSource: Boolean,
|
val isFromSource: Boolean,
|
||||||
val episodes: List<EpisodeItem>,
|
val episodes: List<EpisodeList.Item>,
|
||||||
val trackItems: List<AnimeTrackItem> = emptyList(),
|
val trackItems: List<AnimeTrackItem> = emptyList(),
|
||||||
val isRefreshingData: Boolean = false,
|
val isRefreshingData: Boolean = false,
|
||||||
val dialog: Dialog? = null,
|
val dialog: Dialog? = null,
|
||||||
|
@ -1075,6 +1078,33 @@ class AnimeScreenModel(
|
||||||
episodes.applyFilters(anime).toList()
|
episodes.applyFilters(anime).toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val episodeListItems by lazy {
|
||||||
|
processedEpisodes.insertSeparators { before, after ->
|
||||||
|
val (lowerEpisode, higherEpisode) = if (anime.sortDescending()) {
|
||||||
|
after to before
|
||||||
|
} else {
|
||||||
|
before to after
|
||||||
|
}
|
||||||
|
if (higherEpisode == null) return@insertSeparators null
|
||||||
|
|
||||||
|
if (lowerEpisode == null) {
|
||||||
|
floor(higherEpisode.episode.episodeNumber)
|
||||||
|
.toInt()
|
||||||
|
.minus(1)
|
||||||
|
.coerceAtLeast(0)
|
||||||
|
} else {
|
||||||
|
calculateEpisodeGap(higherEpisode.episode, lowerEpisode.episode)
|
||||||
|
}
|
||||||
|
.takeIf { it > 0 }
|
||||||
|
?.let { missingCount ->
|
||||||
|
EpisodeList.MissingCount(
|
||||||
|
id = "${lowerEpisode?.id}-${higherEpisode.id}",
|
||||||
|
count = missingCount,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val trackingAvailable: Boolean
|
val trackingAvailable: Boolean
|
||||||
get() = trackItems.isNotEmpty()
|
get() = trackItems.isNotEmpty()
|
||||||
|
|
||||||
|
@ -1093,7 +1123,7 @@ class AnimeScreenModel(
|
||||||
* Applies the view filters to the list of episodes obtained from the database.
|
* Applies the view filters to the list of episodes obtained from the database.
|
||||||
* @return an observable of the list of episodes filtered and sorted.
|
* @return an observable of the list of episodes filtered and sorted.
|
||||||
*/
|
*/
|
||||||
private fun List<EpisodeItem>.applyFilters(anime: Anime): Sequence<EpisodeItem> {
|
private fun List<EpisodeList.Item>.applyFilters(anime: Anime): Sequence<EpisodeList.Item> {
|
||||||
val isLocalAnime = anime.isLocal()
|
val isLocalAnime = anime.isLocal()
|
||||||
val unseenFilter = anime.unseenFilter
|
val unseenFilter = anime.unseenFilter
|
||||||
val downloadedFilter = anime.downloadedFilter
|
val downloadedFilter = anime.downloadedFilter
|
||||||
|
@ -1114,11 +1144,21 @@ class AnimeScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class EpisodeItem(
|
sealed class EpisodeList {
|
||||||
|
@Immutable
|
||||||
|
data class MissingCount(
|
||||||
|
val id: String,
|
||||||
|
val count: Int,
|
||||||
|
) : EpisodeList()
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class Item(
|
||||||
val episode: Episode,
|
val episode: Episode,
|
||||||
val downloadState: AnimeDownload.State,
|
val downloadState: AnimeDownload.State,
|
||||||
val downloadProgress: Int,
|
val downloadProgress: Int,
|
||||||
val selected: Boolean = false,
|
val selected: Boolean = false,
|
||||||
) {
|
) : EpisodeList() {
|
||||||
|
val id = episode.id
|
||||||
val isDownloaded = downloadState == AnimeDownload.State.DOWNLOADED
|
val isDownloaded = downloadState == AnimeDownload.State.DOWNLOADED
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.core.util.addOrRemove
|
import eu.kanade.core.util.addOrRemove
|
||||||
|
import eu.kanade.core.util.insertSeparators
|
||||||
import eu.kanade.domain.entries.manga.interactor.UpdateManga
|
import eu.kanade.domain.entries.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.entries.manga.model.downloadedFilter
|
import eu.kanade.domain.entries.manga.model.downloadedFilter
|
||||||
import eu.kanade.domain.entries.manga.model.toSManga
|
import eu.kanade.domain.entries.manga.model.toSManga
|
||||||
|
@ -71,12 +72,14 @@ import tachiyomi.domain.items.chapter.model.Chapter
|
||||||
import tachiyomi.domain.items.chapter.model.ChapterUpdate
|
import tachiyomi.domain.items.chapter.model.ChapterUpdate
|
||||||
import tachiyomi.domain.items.chapter.model.NoChaptersException
|
import tachiyomi.domain.items.chapter.model.NoChaptersException
|
||||||
import tachiyomi.domain.items.chapter.service.getChapterSort
|
import tachiyomi.domain.items.chapter.service.getChapterSort
|
||||||
|
import tachiyomi.domain.items.service.calculateChapterGap
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.source.manga.service.MangaSourceManager
|
import tachiyomi.domain.source.manga.service.MangaSourceManager
|
||||||
import tachiyomi.domain.track.manga.interactor.GetMangaTracks
|
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.floor
|
||||||
|
|
||||||
class MangaScreenModel(
|
class MangaScreenModel(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
|
@ -120,10 +123,10 @@ class MangaScreenModel(
|
||||||
private val isFavorited: Boolean
|
private val isFavorited: Boolean
|
||||||
get() = manga?.favorite ?: false
|
get() = manga?.favorite ?: false
|
||||||
|
|
||||||
private val allChapters: List<ChapterItem>?
|
private val allChapters: List<ChapterList.Item>?
|
||||||
get() = successState?.chapters
|
get() = successState?.chapters
|
||||||
|
|
||||||
private val filteredChapters: List<ChapterItem>?
|
private val filteredChapters: List<ChapterList.Item>?
|
||||||
get() = successState?.processedChapters
|
get() = successState?.processedChapters
|
||||||
|
|
||||||
val chapterSwipeStartAction = libraryPreferences.swipeChapterEndAction().get()
|
val chapterSwipeStartAction = libraryPreferences.swipeChapterEndAction().get()
|
||||||
|
@ -166,7 +169,7 @@ class MangaScreenModel(
|
||||||
updateSuccessState {
|
updateSuccessState {
|
||||||
it.copy(
|
it.copy(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
chapters = chapters.toChapterItems(manga),
|
chapters = chapters.toChapterListItems(manga),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,7 +180,7 @@ class MangaScreenModel(
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
val manga = getMangaAndChapters.awaitManga(mangaId)
|
val manga = getMangaAndChapters.awaitManga(mangaId)
|
||||||
val chapters = getMangaAndChapters.awaitChapters(mangaId)
|
val chapters = getMangaAndChapters.awaitChapters(mangaId)
|
||||||
.toChapterItems(manga)
|
.toChapterListItems(manga)
|
||||||
|
|
||||||
if (!manga.favorite) {
|
if (!manga.favorite) {
|
||||||
setMangaDefaultChapterFlags.await(manga)
|
setMangaDefaultChapterFlags.await(manga)
|
||||||
|
@ -473,7 +476,7 @@ class MangaScreenModel(
|
||||||
|
|
||||||
private fun updateDownloadState(download: MangaDownload) {
|
private fun updateDownloadState(download: MangaDownload) {
|
||||||
updateSuccessState { successState ->
|
updateSuccessState { successState ->
|
||||||
val modifiedIndex = successState.chapters.indexOfFirst { it.chapter.id == download.chapter.id }
|
val modifiedIndex = successState.chapters.indexOfFirst { it.id == download.chapter.id }
|
||||||
if (modifiedIndex < 0) return@updateSuccessState successState
|
if (modifiedIndex < 0) return@updateSuccessState successState
|
||||||
|
|
||||||
val newChapters = successState.chapters.toMutableList().apply {
|
val newChapters = successState.chapters.toMutableList().apply {
|
||||||
|
@ -485,7 +488,7 @@ class MangaScreenModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Chapter>.toChapterItems(manga: Manga): List<ChapterItem> {
|
private fun List<Chapter>.toChapterListItems(manga: Manga): List<ChapterList.Item> {
|
||||||
val isLocal = manga.isLocal()
|
val isLocal = manga.isLocal()
|
||||||
return map { chapter ->
|
return map { chapter ->
|
||||||
val activeDownload = if (isLocal) {
|
val activeDownload = if (isLocal) {
|
||||||
|
@ -509,7 +512,7 @@ class MangaScreenModel(
|
||||||
else -> MangaDownload.State.NOT_DOWNLOADED
|
else -> MangaDownload.State.NOT_DOWNLOADED
|
||||||
}
|
}
|
||||||
|
|
||||||
ChapterItem(
|
ChapterList.Item(
|
||||||
chapter = chapter,
|
chapter = chapter,
|
||||||
downloadState = downloadState,
|
downloadState = downloadState,
|
||||||
downloadProgress = activeDownload?.progress ?: 0,
|
downloadProgress = activeDownload?.progress ?: 0,
|
||||||
|
@ -557,7 +560,7 @@ class MangaScreenModel(
|
||||||
/**
|
/**
|
||||||
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
|
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
|
||||||
*/
|
*/
|
||||||
fun chapterSwipe(chapterItem: ChapterItem, swipeAction: LibraryPreferences.ChapterSwipeAction) {
|
fun chapterSwipe(chapterItem: ChapterList.Item, swipeAction: LibraryPreferences.ChapterSwipeAction) {
|
||||||
screenModelScope.launch {
|
screenModelScope.launch {
|
||||||
executeChapterSwipeAction(chapterItem, swipeAction)
|
executeChapterSwipeAction(chapterItem, swipeAction)
|
||||||
}
|
}
|
||||||
|
@ -567,7 +570,7 @@ class MangaScreenModel(
|
||||||
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
|
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
|
||||||
*/
|
*/
|
||||||
private fun executeChapterSwipeAction(
|
private fun executeChapterSwipeAction(
|
||||||
chapterItem: ChapterItem,
|
chapterItem: ChapterList.Item,
|
||||||
swipeAction: LibraryPreferences.ChapterSwipeAction,
|
swipeAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
) {
|
) {
|
||||||
val chapter = chapterItem.chapter
|
val chapter = chapterItem.chapter
|
||||||
|
@ -648,7 +651,7 @@ class MangaScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runChapterDownloadActions(
|
fun runChapterDownloadActions(
|
||||||
items: List<ChapterItem>,
|
items: List<ChapterList.Item>,
|
||||||
action: ChapterDownloadAction,
|
action: ChapterDownloadAction,
|
||||||
) {
|
) {
|
||||||
when (action) {
|
when (action) {
|
||||||
|
@ -663,7 +666,7 @@ class MangaScreenModel(
|
||||||
startDownload(listOf(chapter), true)
|
startDownload(listOf(chapter), true)
|
||||||
}
|
}
|
||||||
ChapterDownloadAction.CANCEL -> {
|
ChapterDownloadAction.CANCEL -> {
|
||||||
val chapterId = items.singleOrNull()?.chapter?.id ?: return
|
val chapterId = items.singleOrNull()?.id ?: return
|
||||||
cancelDownload(chapterId)
|
cancelDownload(chapterId)
|
||||||
}
|
}
|
||||||
ChapterDownloadAction.DELETE -> {
|
ChapterDownloadAction.DELETE -> {
|
||||||
|
@ -873,14 +876,14 @@ class MangaScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleSelection(
|
fun toggleSelection(
|
||||||
item: ChapterItem,
|
item: ChapterList.Item,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
userSelected: Boolean = false,
|
userSelected: Boolean = false,
|
||||||
fromLongPress: Boolean = false,
|
fromLongPress: Boolean = false,
|
||||||
) {
|
) {
|
||||||
updateSuccessState { successState ->
|
updateSuccessState { successState ->
|
||||||
val newChapters = successState.processedChapters.toMutableList().apply {
|
val newChapters = successState.processedChapters.toMutableList().apply {
|
||||||
val selectedIndex = successState.processedChapters.indexOfFirst { it.chapter.id == item.chapter.id }
|
val selectedIndex = successState.processedChapters.indexOfFirst { it.id == item.chapter.id }
|
||||||
if (selectedIndex < 0) return@apply
|
if (selectedIndex < 0) return@apply
|
||||||
|
|
||||||
val selectedItem = get(selectedIndex)
|
val selectedItem = get(selectedIndex)
|
||||||
|
@ -888,7 +891,7 @@ class MangaScreenModel(
|
||||||
|
|
||||||
val firstSelection = none { it.selected }
|
val firstSelection = none { it.selected }
|
||||||
set(selectedIndex, selectedItem.copy(selected = selected))
|
set(selectedIndex, selectedItem.copy(selected = selected))
|
||||||
selectedChapterIds.addOrRemove(item.chapter.id, selected)
|
selectedChapterIds.addOrRemove(item.id, selected)
|
||||||
|
|
||||||
if (selected && userSelected && fromLongPress) {
|
if (selected && userSelected && fromLongPress) {
|
||||||
if (firstSelection) {
|
if (firstSelection) {
|
||||||
|
@ -911,7 +914,7 @@ class MangaScreenModel(
|
||||||
range.forEach {
|
range.forEach {
|
||||||
val inbetweenItem = get(it)
|
val inbetweenItem = get(it)
|
||||||
if (!inbetweenItem.selected) {
|
if (!inbetweenItem.selected) {
|
||||||
selectedChapterIds.add(inbetweenItem.chapter.id)
|
selectedChapterIds.add(inbetweenItem.id)
|
||||||
set(it, inbetweenItem.copy(selected = true))
|
set(it, inbetweenItem.copy(selected = true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -939,7 +942,7 @@ class MangaScreenModel(
|
||||||
fun toggleAllSelection(selected: Boolean) {
|
fun toggleAllSelection(selected: Boolean) {
|
||||||
updateSuccessState { successState ->
|
updateSuccessState { successState ->
|
||||||
val newChapters = successState.chapters.map {
|
val newChapters = successState.chapters.map {
|
||||||
selectedChapterIds.addOrRemove(it.chapter.id, selected)
|
selectedChapterIds.addOrRemove(it.id, selected)
|
||||||
it.copy(selected = selected)
|
it.copy(selected = selected)
|
||||||
}
|
}
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
|
@ -951,7 +954,7 @@ class MangaScreenModel(
|
||||||
fun invertSelection() {
|
fun invertSelection() {
|
||||||
updateSuccessState { successState ->
|
updateSuccessState { successState ->
|
||||||
val newChapters = successState.chapters.map {
|
val newChapters = successState.chapters.map {
|
||||||
selectedChapterIds.addOrRemove(it.chapter.id, !it.selected)
|
selectedChapterIds.addOrRemove(it.id, !it.selected)
|
||||||
it.copy(selected = !it.selected)
|
it.copy(selected = !it.selected)
|
||||||
}
|
}
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
|
@ -1033,7 +1036,7 @@ class MangaScreenModel(
|
||||||
val manga: Manga,
|
val manga: Manga,
|
||||||
val source: MangaSource,
|
val source: MangaSource,
|
||||||
val isFromSource: Boolean,
|
val isFromSource: Boolean,
|
||||||
val chapters: List<ChapterItem>,
|
val chapters: List<ChapterList.Item>,
|
||||||
val trackItems: List<MangaTrackItem> = emptyList(),
|
val trackItems: List<MangaTrackItem> = emptyList(),
|
||||||
val isRefreshingData: Boolean = false,
|
val isRefreshingData: Boolean = false,
|
||||||
val dialog: Dialog? = null,
|
val dialog: Dialog? = null,
|
||||||
|
@ -1044,6 +1047,33 @@ class MangaScreenModel(
|
||||||
chapters.applyFilters(manga).toList()
|
chapters.applyFilters(manga).toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val chapterListItems by lazy {
|
||||||
|
processedChapters.insertSeparators { before, after ->
|
||||||
|
val (lowerChapter, higherChapter) = if (manga.sortDescending()) {
|
||||||
|
after to before
|
||||||
|
} else {
|
||||||
|
before to after
|
||||||
|
}
|
||||||
|
if (higherChapter == null) return@insertSeparators null
|
||||||
|
|
||||||
|
if (lowerChapter == null) {
|
||||||
|
floor(higherChapter.chapter.chapterNumber)
|
||||||
|
.toInt()
|
||||||
|
.minus(1)
|
||||||
|
.coerceAtLeast(0)
|
||||||
|
} else {
|
||||||
|
calculateChapterGap(higherChapter.chapter, lowerChapter.chapter)
|
||||||
|
}
|
||||||
|
.takeIf { it > 0 }
|
||||||
|
?.let { missingCount ->
|
||||||
|
ChapterList.MissingCount(
|
||||||
|
id = "${lowerChapter?.id}-${higherChapter.id}",
|
||||||
|
count = missingCount,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val trackingAvailable: Boolean
|
val trackingAvailable: Boolean
|
||||||
get() = trackItems.isNotEmpty()
|
get() = trackItems.isNotEmpty()
|
||||||
|
|
||||||
|
@ -1054,7 +1084,7 @@ class MangaScreenModel(
|
||||||
* Applies the view filters to the list of chapters obtained from the database.
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
* @return an observable of the list of chapters filtered and sorted.
|
* @return an observable of the list of chapters filtered and sorted.
|
||||||
*/
|
*/
|
||||||
private fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> {
|
private fun List<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item> {
|
||||||
val isLocalManga = manga.isLocal()
|
val isLocalManga = manga.isLocal()
|
||||||
val unreadFilter = manga.unreadFilter
|
val unreadFilter = manga.unreadFilter
|
||||||
val downloadedFilter = manga.downloadedFilter
|
val downloadedFilter = manga.downloadedFilter
|
||||||
|
@ -1075,11 +1105,21 @@ class MangaScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ChapterItem(
|
sealed class ChapterList {
|
||||||
|
@Immutable
|
||||||
|
data class MissingCount(
|
||||||
|
val id: String,
|
||||||
|
val count: Int,
|
||||||
|
) : ChapterList()
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class Item(
|
||||||
val chapter: Chapter,
|
val chapter: Chapter,
|
||||||
val downloadState: MangaDownload.State,
|
val downloadState: MangaDownload.State,
|
||||||
val downloadProgress: Int,
|
val downloadProgress: Int,
|
||||||
val selected: Boolean = false,
|
val selected: Boolean = false,
|
||||||
) {
|
) : ChapterList() {
|
||||||
|
val id = chapter.id
|
||||||
val isDownloaded = downloadState == MangaDownload.State.DOWNLOADED
|
val isDownloaded = downloadState == MangaDownload.State.DOWNLOADED
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ 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.applyFilter
|
import tachiyomi.domain.entries.applyFilter
|
||||||
import tachiyomi.domain.history.anime.interactor.GetNextEpisodes
|
import tachiyomi.domain.history.anime.interactor.GetNextEpisodes
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
import tachiyomi.domain.library.anime.LibraryAnime
|
import tachiyomi.domain.library.anime.LibraryAnime
|
||||||
import tachiyomi.domain.library.anime.model.AnimeLibrarySort
|
import tachiyomi.domain.library.anime.model.AnimeLibrarySort
|
||||||
|
@ -79,7 +79,7 @@ class AnimeLibraryScreenModel(
|
||||||
private val getCategories: GetVisibleAnimeCategories = Injekt.get(),
|
private val getCategories: GetVisibleAnimeCategories = Injekt.get(),
|
||||||
private val getTracksPerAnime: GetTracksPerAnime = Injekt.get(),
|
private val getTracksPerAnime: GetTracksPerAnime = Injekt.get(),
|
||||||
private val getNextEpisodes: GetNextEpisodes = Injekt.get(),
|
private val getNextEpisodes: GetNextEpisodes = Injekt.get(),
|
||||||
private val getEpisodesByAnimeId: GetEpisodeByAnimeId = Injekt.get(),
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId = Injekt.get(),
|
||||||
private val setSeenStatus: SetSeenStatus = Injekt.get(),
|
private val setSeenStatus: SetSeenStatus = Injekt.get(),
|
||||||
private val updateAnime: UpdateAnime = Injekt.get(),
|
private val updateAnime: UpdateAnime = Injekt.get(),
|
||||||
private val setAnimeCategories: SetAnimeCategories = Injekt.get(),
|
private val setAnimeCategories: SetAnimeCategories = Injekt.get(),
|
||||||
|
|
|
@ -53,7 +53,7 @@ import tachiyomi.domain.entries.manga.interactor.GetLibraryManga
|
||||||
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.history.manga.interactor.GetNextChapters
|
import tachiyomi.domain.history.manga.interactor.GetNextChapters
|
||||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
import tachiyomi.domain.items.chapter.model.Chapter
|
||||||
import tachiyomi.domain.library.manga.LibraryManga
|
import tachiyomi.domain.library.manga.LibraryManga
|
||||||
import tachiyomi.domain.library.manga.model.MangaLibrarySort
|
import tachiyomi.domain.library.manga.model.MangaLibrarySort
|
||||||
|
@ -79,7 +79,7 @@ class MangaLibraryScreenModel(
|
||||||
private val getCategories: GetVisibleMangaCategories = Injekt.get(),
|
private val getCategories: GetVisibleMangaCategories = Injekt.get(),
|
||||||
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
|
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
|
||||||
private val getNextChapters: GetNextChapters = Injekt.get(),
|
private val getNextChapters: GetNextChapters = Injekt.get(),
|
||||||
private val getChaptersByMangaId: GetChapterByMangaId = Injekt.get(),
|
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
|
||||||
private val setReadStatus: SetReadStatus = Injekt.get(),
|
private val setReadStatus: SetReadStatus = Injekt.get(),
|
||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga = Injekt.get(),
|
||||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||||
|
|
|
@ -31,7 +31,6 @@ import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.lang.launchIO
|
import tachiyomi.core.util.lang.launchIO
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
@ -42,7 +41,7 @@ import tachiyomi.domain.entries.anime.interactor.GetAnime
|
||||||
import tachiyomi.domain.entries.anime.model.Anime
|
import tachiyomi.domain.entries.anime.model.Anime
|
||||||
import tachiyomi.domain.history.anime.interactor.UpsertAnimeHistory
|
import tachiyomi.domain.history.anime.interactor.UpsertAnimeHistory
|
||||||
import tachiyomi.domain.history.anime.model.AnimeHistoryUpdate
|
import tachiyomi.domain.history.anime.model.AnimeHistoryUpdate
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
import tachiyomi.domain.items.episode.model.EpisodeUpdate
|
import tachiyomi.domain.items.episode.model.EpisodeUpdate
|
||||||
|
@ -81,7 +80,7 @@ class ExternalIntents {
|
||||||
): Intent? {
|
): Intent? {
|
||||||
anime = getAnime.await(animeId!!) ?: return null
|
anime = getAnime.await(animeId!!) ?: return null
|
||||||
source = sourceManager.get(anime.source) ?: return null
|
source = sourceManager.get(anime.source) ?: return null
|
||||||
episode = getEpisodeByAnimeId.await(anime.id).find { it.id == episodeId } ?: return null
|
episode = getEpisodesByAnimeId.await(anime.id).find { it.id == episodeId } ?: return null
|
||||||
|
|
||||||
val video = chosenVideo
|
val video = chosenVideo
|
||||||
?: EpisodeLoader.getLinks(episode, anime, source).asFlow().first().firstOrNull()
|
?: EpisodeLoader.getLinks(episode, anime, source).asFlow().first().firstOrNull()
|
||||||
|
@ -392,7 +391,7 @@ class ExternalIntents {
|
||||||
private val updateEpisode: UpdateEpisode = Injekt.get()
|
private val updateEpisode: UpdateEpisode = Injekt.get()
|
||||||
private val getAnime: GetAnime = Injekt.get()
|
private val getAnime: GetAnime = Injekt.get()
|
||||||
private val sourceManager: AnimeSourceManager = Injekt.get()
|
private val sourceManager: AnimeSourceManager = Injekt.get()
|
||||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get()
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId = Injekt.get()
|
||||||
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 downloadManager: AnimeDownloadManager by injectLazy()
|
private val downloadManager: AnimeDownloadManager by injectLazy()
|
||||||
|
@ -469,7 +468,7 @@ class ExternalIntents {
|
||||||
else -> throw NotImplementedError("Unknown sorting method")
|
else -> throw NotImplementedError("Unknown sorting method")
|
||||||
}
|
}
|
||||||
|
|
||||||
val episodes = getEpisodeByAnimeId.await(anime.id)
|
val episodes = getEpisodesByAnimeId.await(anime.id)
|
||||||
.sortedWith { e1, e2 -> sortFunction(e1, e2) }
|
.sortedWith { e1, e2 -> sortFunction(e1, e2) }
|
||||||
|
|
||||||
val currentEpisodePosition = episodes.indexOf(episode)
|
val currentEpisodePosition = episodes.indexOf(episode)
|
||||||
|
|
|
@ -59,7 +59,7 @@ import tachiyomi.domain.entries.anime.model.Anime
|
||||||
import tachiyomi.domain.history.anime.interactor.GetNextEpisodes
|
import tachiyomi.domain.history.anime.interactor.GetNextEpisodes
|
||||||
import tachiyomi.domain.history.anime.interactor.UpsertAnimeHistory
|
import tachiyomi.domain.history.anime.interactor.UpsertAnimeHistory
|
||||||
import tachiyomi.domain.history.anime.model.AnimeHistoryUpdate
|
import tachiyomi.domain.history.anime.model.AnimeHistoryUpdate
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||||
import tachiyomi.domain.items.episode.model.EpisodeUpdate
|
import tachiyomi.domain.items.episode.model.EpisodeUpdate
|
||||||
import tachiyomi.domain.items.episode.service.getEpisodeSort
|
import tachiyomi.domain.items.episode.service.getEpisodeSort
|
||||||
|
@ -81,7 +81,7 @@ class PlayerViewModel @JvmOverloads constructor(
|
||||||
private val trackEpisode: TrackEpisode = Injekt.get(),
|
private val trackEpisode: TrackEpisode = Injekt.get(),
|
||||||
private val getAnime: GetAnime = Injekt.get(),
|
private val getAnime: GetAnime = Injekt.get(),
|
||||||
private val getNextEpisodes: GetNextEpisodes = Injekt.get(),
|
private val getNextEpisodes: GetNextEpisodes = Injekt.get(),
|
||||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get(),
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId = Injekt.get(),
|
||||||
private val getTracks: GetAnimeTracks = Injekt.get(),
|
private val getTracks: GetAnimeTracks = Injekt.get(),
|
||||||
private val upsertHistory: UpsertAnimeHistory = Injekt.get(),
|
private val upsertHistory: UpsertAnimeHistory = Injekt.get(),
|
||||||
private val updateEpisode: UpdateEpisode = Injekt.get(),
|
private val updateEpisode: UpdateEpisode = Injekt.get(),
|
||||||
|
@ -284,7 +284,7 @@ class PlayerViewModel @JvmOverloads constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun initEpisodeList(anime: Anime): List<Episode> {
|
private fun initEpisodeList(anime: Anime): List<Episode> {
|
||||||
val episodes = runBlocking { getEpisodeByAnimeId.await(anime.id) }
|
val episodes = runBlocking { getEpisodesByAnimeId.await(anime.id) }
|
||||||
|
|
||||||
return episodes
|
return episodes
|
||||||
.sortedWith(getEpisodeSort(anime, sortDescending = false))
|
.sortedWith(getEpisodeSort(anime, sortDescending = false))
|
||||||
|
|
|
@ -66,7 +66,7 @@ import tachiyomi.domain.entries.manga.model.Manga
|
||||||
import tachiyomi.domain.history.manga.interactor.GetNextChapters
|
import tachiyomi.domain.history.manga.interactor.GetNextChapters
|
||||||
import tachiyomi.domain.history.manga.interactor.UpsertMangaHistory
|
import tachiyomi.domain.history.manga.interactor.UpsertMangaHistory
|
||||||
import tachiyomi.domain.history.manga.model.MangaHistoryUpdate
|
import tachiyomi.domain.history.manga.model.MangaHistoryUpdate
|
||||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.items.chapter.model.ChapterUpdate
|
import tachiyomi.domain.items.chapter.model.ChapterUpdate
|
||||||
import tachiyomi.domain.items.chapter.service.getChapterSort
|
import tachiyomi.domain.items.chapter.service.getChapterSort
|
||||||
|
@ -92,7 +92,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||||
private val trackPreferences: TrackPreferences = Injekt.get(),
|
private val trackPreferences: TrackPreferences = Injekt.get(),
|
||||||
private val trackChapter: TrackChapter = Injekt.get(),
|
private val trackChapter: TrackChapter = Injekt.get(),
|
||||||
private val getManga: GetManga = Injekt.get(),
|
private val getManga: GetManga = Injekt.get(),
|
||||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
|
||||||
private val getNextChapters: GetNextChapters = Injekt.get(),
|
private val getNextChapters: GetNextChapters = Injekt.get(),
|
||||||
private val upsertHistory: UpsertMangaHistory = Injekt.get(),
|
private val upsertHistory: UpsertMangaHistory = Injekt.get(),
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||||
|
@ -147,7 +147,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||||
*/
|
*/
|
||||||
private val chapterList by lazy {
|
private val chapterList by lazy {
|
||||||
val manga = manga!!
|
val manga = manga!!
|
||||||
val chapters = runBlocking { getChapterByMangaId.await(manga.id) }
|
val chapters = runBlocking { getChaptersByMangaId.await(manga.id) }
|
||||||
|
|
||||||
val selectedChapter = chapters.find { it.id == chapterId }
|
val selectedChapter = chapters.find { it.id == chapterId }
|
||||||
?: error("Requested chapter of id $chapterId not found in chapter list")
|
?: error("Requested chapter of id $chapterId not found in chapter list")
|
||||||
|
|
|
@ -16,7 +16,7 @@ import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import tachiyomi.core.util.lang.launchIO
|
import tachiyomi.core.util.lang.launchIO
|
||||||
import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime
|
import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.library.anime.LibraryAnime
|
import tachiyomi.domain.library.anime.LibraryAnime
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_HAS_UNVIEWED
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_HAS_UNVIEWED
|
||||||
|
@ -31,7 +31,7 @@ import uy.kohesive.injekt.api.get
|
||||||
class AnimeStatsScreenModel(
|
class AnimeStatsScreenModel(
|
||||||
private val downloadManager: AnimeDownloadManager = Injekt.get(),
|
private val downloadManager: AnimeDownloadManager = Injekt.get(),
|
||||||
private val getAnimelibAnime: GetLibraryAnime = Injekt.get(),
|
private val getAnimelibAnime: GetLibraryAnime = Injekt.get(),
|
||||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get(),
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId = Injekt.get(),
|
||||||
private val getTracks: GetAnimeTracks = Injekt.get(),
|
private val getTracks: GetAnimeTracks = Injekt.get(),
|
||||||
private val preferences: LibraryPreferences = Injekt.get(),
|
private val preferences: LibraryPreferences = Injekt.get(),
|
||||||
private val trackerManager: TrackerManager = Injekt.get(),
|
private val trackerManager: TrackerManager = Injekt.get(),
|
||||||
|
@ -128,7 +128,7 @@ class AnimeStatsScreenModel(
|
||||||
private suspend fun getWatchTime(libraryAnimeList: List<LibraryAnime>): Long {
|
private suspend fun getWatchTime(libraryAnimeList: List<LibraryAnime>): Long {
|
||||||
var watchTime = 0L
|
var watchTime = 0L
|
||||||
libraryAnimeList.forEach { libraryAnime ->
|
libraryAnimeList.forEach { libraryAnime ->
|
||||||
getEpisodeByAnimeId.await(libraryAnime.anime.id).forEach { episode ->
|
getEpisodesByAnimeId.await(libraryAnime.anime.id).forEach { episode ->
|
||||||
watchTime += if (episode.seen) {
|
watchTime += if (episode.seen) {
|
||||||
episode.totalSeconds
|
episode.totalSeconds
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.util.chapter
|
||||||
|
|
||||||
import eu.kanade.domain.items.chapter.model.applyFilters
|
import eu.kanade.domain.items.chapter.model.applyFilters
|
||||||
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager
|
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager
|
||||||
import eu.kanade.tachiyomi.ui.entries.manga.ChapterItem
|
import eu.kanade.tachiyomi.ui.entries.manga.ChapterList
|
||||||
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
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ fun List<Chapter>.getNextUnread(manga: Manga, downloadManager: MangaDownloadMana
|
||||||
/**
|
/**
|
||||||
* Gets next unread chapter with filters and sorting applied
|
* Gets next unread chapter with filters and sorting applied
|
||||||
*/
|
*/
|
||||||
fun List<ChapterItem>.getNextUnread(manga: Manga): Chapter? {
|
fun List<ChapterList.Item>.getNextUnread(manga: Manga): Chapter? {
|
||||||
return applyFilters(manga).let { chapters ->
|
return applyFilters(manga).let { chapters ->
|
||||||
if (manga.sortDescending()) {
|
if (manga.sortDescending()) {
|
||||||
chapters.findLast { !it.chapter.read }
|
chapters.findLast { !it.chapter.read }
|
||||||
|
|
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.util.episode
|
||||||
|
|
||||||
import eu.kanade.domain.items.episode.model.applyFilters
|
import eu.kanade.domain.items.episode.model.applyFilters
|
||||||
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
||||||
import eu.kanade.tachiyomi.ui.entries.anime.EpisodeItem
|
import eu.kanade.tachiyomi.ui.entries.anime.EpisodeList
|
||||||
import tachiyomi.domain.entries.anime.model.Anime
|
import tachiyomi.domain.entries.anime.model.Anime
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ fun List<Episode>.getNextUnseen(anime: Anime, downloadManager: AnimeDownloadMana
|
||||||
/**
|
/**
|
||||||
* Gets next unseen episode with filters and sorting applied
|
* Gets next unseen episode with filters and sorting applied
|
||||||
*/
|
*/
|
||||||
fun List<EpisodeItem>.getNextUnseen(anime: Anime): Episode? {
|
fun List<EpisodeList.Item>.getNextUnseen(anime: Anime): Episode? {
|
||||||
return applyFilters(anime).let { episodes ->
|
return applyFilters(anime).let { episodes ->
|
||||||
if (anime.sortDescending()) {
|
if (anime.sortDescending()) {
|
||||||
episodes.findLast { !it.episode.seen }
|
episodes.findLast { !it.episode.seen }
|
||||||
|
|
|
@ -55,5 +55,5 @@ subprojects {
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Delete>("clean") {
|
tasks.register<Delete>("clean") {
|
||||||
delete(rootProject.buildDir)
|
delete(rootProject.layout.buildDirectory)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ dependencies {
|
||||||
|
|
||||||
api(libs.okhttp.core)
|
api(libs.okhttp.core)
|
||||||
api(libs.okhttp.logging)
|
api(libs.okhttp.logging)
|
||||||
|
api(libs.okhttp.brotli)
|
||||||
api(libs.okhttp.dnsoverhttps)
|
api(libs.okhttp.dnsoverhttps)
|
||||||
api(libs.okio)
|
api(libs.okio)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.kanade.tachiyomi.core.security
|
package eu.kanade.tachiyomi.core.security
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.core.R
|
import eu.kanade.tachiyomi.core.R
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
|
|
||||||
|
@ -20,7 +21,10 @@ class SecurityPreferences(
|
||||||
* For app lock. Will be set when there is a pending timed lock.
|
* For app lock. Will be set when there is a pending timed lock.
|
||||||
* Otherwise this pref should be deleted.
|
* Otherwise this pref should be deleted.
|
||||||
*/
|
*/
|
||||||
fun lastAppClosed() = preferenceStore.getLong("last_app_closed", 0)
|
fun lastAppClosed() = preferenceStore.getLong(
|
||||||
|
Preference.appStateKey("last_app_closed"),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
enum class SecureScreenMode(val titleResId: Int) {
|
enum class SecureScreenMode(val titleResId: Int) {
|
||||||
ALWAYS(R.string.lock_always),
|
ALWAYS(R.string.lock_always),
|
||||||
|
|
|
@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
|
||||||
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
|
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.brotli.BrotliInterceptor
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -29,6 +30,7 @@ class NetworkHelper(
|
||||||
maxSize = 5L * 1024 * 1024, // 5 MiB
|
maxSize = 5L * 1024 * 1024, // 5 MiB
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.addInterceptor(BrotliInterceptor)
|
||||||
.addInterceptor(UncaughtExceptionInterceptor())
|
.addInterceptor(UncaughtExceptionInterceptor())
|
||||||
.addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider))
|
.addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider))
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,30 @@ object DiskUtil {
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the total space for the disk that a file path points to, in bytes.
|
||||||
|
*/
|
||||||
|
fun getTotalStorageSpace(file: File): Long {
|
||||||
|
return try {
|
||||||
|
val stat = StatFs(file.absolutePath)
|
||||||
|
stat.blockCountLong * stat.blockSizeLong
|
||||||
|
} catch (_: Exception) {
|
||||||
|
-1L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the available space for the disk that a file path points to, in bytes.
|
||||||
|
*/
|
||||||
|
fun getAvailableStorageSpace(file: File): Long {
|
||||||
|
return try {
|
||||||
|
val stat = StatFs(file.absolutePath)
|
||||||
|
stat.availableBlocksLong * stat.blockSizeLong
|
||||||
|
} catch (_: Exception) {
|
||||||
|
-1L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the available space for the disk that a file path points to, in bytes.
|
* Gets the available space for the disk that a file path points to, in bytes.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,6 +21,32 @@ interface Preference<T> {
|
||||||
fun changes(): Flow<T>
|
fun changes(): Flow<T>
|
||||||
|
|
||||||
fun stateIn(scope: CoroutineScope): StateFlow<T>
|
fun stateIn(scope: CoroutineScope): StateFlow<T>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* A preference that should not be exposed in places like backups without user consent.
|
||||||
|
*/
|
||||||
|
fun isPrivate(key: String): Boolean {
|
||||||
|
return key.startsWith(PRIVATE_PREFIX)
|
||||||
|
}
|
||||||
|
fun privateKey(key: String): String {
|
||||||
|
return "${PRIVATE_PREFIX}$key"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A preference used for internal app state that isn't really a user preference
|
||||||
|
* and therefore should not be in places like backups.
|
||||||
|
*/
|
||||||
|
fun isAppState(key: String): Boolean {
|
||||||
|
return key.startsWith(APP_STATE_PREFIX)
|
||||||
|
}
|
||||||
|
fun appStateKey(key: String): String {
|
||||||
|
return "${APP_STATE_PREFIX}$key"
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val APP_STATE_PREFIX = "__APP_STATE_"
|
||||||
|
private const val PRIVATE_PREFIX = "__PRIVATE_"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T, R : T> Preference<T>.getAndSet(crossinline block: (T) -> R) = set(
|
inline fun <reified T, R : T> Preference<T>.getAndSet(crossinline block: (T) -> R) = set(
|
||||||
|
|
|
@ -2,7 +2,7 @@ package tachiyomi.domain.entries.anime.interactor
|
||||||
|
|
||||||
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.items.episode.interactor.GetEpisodeByAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
|
@ -11,7 +11,7 @@ import java.time.temporal.ChronoUnit
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
class AnimeFetchInterval(
|
class AnimeFetchInterval(
|
||||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId,
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun toAnimeUpdateOrNull(
|
suspend fun toAnimeUpdateOrNull(
|
||||||
|
@ -24,7 +24,7 @@ class AnimeFetchInterval(
|
||||||
} else {
|
} else {
|
||||||
window
|
window
|
||||||
}
|
}
|
||||||
val episodes = getEpisodeByAnimeId.await(anime.id)
|
val episodes = getEpisodesByAnimeId.await(anime.id)
|
||||||
val interval = anime.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
|
val interval = anime.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
|
||||||
episodes,
|
episodes,
|
||||||
dateTime.zone,
|
dateTime.zone,
|
||||||
|
|
|
@ -2,7 +2,7 @@ package tachiyomi.domain.entries.manga.interactor
|
||||||
|
|
||||||
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.items.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
import tachiyomi.domain.items.chapter.model.Chapter
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
|
@ -11,7 +11,7 @@ import java.time.temporal.ChronoUnit
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
class MangaFetchInterval(
|
class MangaFetchInterval(
|
||||||
private val getChapterByMangaId: GetChapterByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun toMangaUpdateOrNull(
|
suspend fun toMangaUpdateOrNull(
|
||||||
|
@ -24,7 +24,7 @@ class MangaFetchInterval(
|
||||||
} else {
|
} else {
|
||||||
window
|
window
|
||||||
}
|
}
|
||||||
val chapters = getChapterByMangaId.await(manga.id)
|
val chapters = getChaptersByMangaId.await(manga.id)
|
||||||
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
|
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
|
||||||
chapters,
|
chapters,
|
||||||
dateTime.zone,
|
dateTime.zone,
|
||||||
|
|
|
@ -2,13 +2,13 @@ package tachiyomi.domain.history.anime.interactor
|
||||||
|
|
||||||
import tachiyomi.domain.entries.anime.interactor.GetAnime
|
import tachiyomi.domain.entries.anime.interactor.GetAnime
|
||||||
import tachiyomi.domain.history.anime.repository.AnimeHistoryRepository
|
import tachiyomi.domain.history.anime.repository.AnimeHistoryRepository
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
import tachiyomi.domain.items.episode.service.getEpisodeSort
|
import tachiyomi.domain.items.episode.service.getEpisodeSort
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class GetNextEpisodes(
|
class GetNextEpisodes(
|
||||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId,
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId,
|
||||||
private val getAnime: GetAnime,
|
private val getAnime: GetAnime,
|
||||||
private val historyRepository: AnimeHistoryRepository,
|
private val historyRepository: AnimeHistoryRepository,
|
||||||
) {
|
) {
|
||||||
|
@ -20,7 +20,7 @@ class GetNextEpisodes(
|
||||||
|
|
||||||
suspend fun await(animeId: Long, onlyUnseen: Boolean = true): List<Episode> {
|
suspend fun await(animeId: Long, onlyUnseen: Boolean = true): List<Episode> {
|
||||||
val anime = getAnime.await(animeId) ?: return emptyList()
|
val anime = getAnime.await(animeId) ?: return emptyList()
|
||||||
val episodes = getEpisodeByAnimeId.await(animeId)
|
val episodes = getEpisodesByAnimeId.await(animeId)
|
||||||
.sortedWith(getEpisodeSort(anime, sortDescending = false))
|
.sortedWith(getEpisodeSort(anime, sortDescending = false))
|
||||||
|
|
||||||
return if (onlyUnseen) {
|
return if (onlyUnseen) {
|
||||||
|
|
|
@ -2,13 +2,13 @@ package tachiyomi.domain.history.manga.interactor
|
||||||
|
|
||||||
import tachiyomi.domain.entries.manga.interactor.GetManga
|
import tachiyomi.domain.entries.manga.interactor.GetManga
|
||||||
import tachiyomi.domain.history.manga.repository.MangaHistoryRepository
|
import tachiyomi.domain.history.manga.repository.MangaHistoryRepository
|
||||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
import tachiyomi.domain.items.chapter.model.Chapter
|
||||||
import tachiyomi.domain.items.chapter.service.getChapterSort
|
import tachiyomi.domain.items.chapter.service.getChapterSort
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class GetNextChapters(
|
class GetNextChapters(
|
||||||
private val getChapterByMangaId: GetChapterByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
private val getManga: GetManga,
|
private val getManga: GetManga,
|
||||||
private val historyRepository: MangaHistoryRepository,
|
private val historyRepository: MangaHistoryRepository,
|
||||||
) {
|
) {
|
||||||
|
@ -20,7 +20,7 @@ class GetNextChapters(
|
||||||
|
|
||||||
suspend fun await(mangaId: Long, onlyUnread: Boolean = true): List<Chapter> {
|
suspend fun await(mangaId: Long, onlyUnread: Boolean = true): List<Chapter> {
|
||||||
val manga = getManga.await(mangaId) ?: return emptyList()
|
val manga = getManga.await(mangaId) ?: return emptyList()
|
||||||
val chapters = getChapterByMangaId.await(mangaId)
|
val chapters = getChaptersByMangaId.await(mangaId)
|
||||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||||
|
|
||||||
return if (onlyUnread) {
|
return if (onlyUnread) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import tachiyomi.core.util.system.logcat
|
||||||
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
|
||||||
|
|
||||||
class GetChapterByMangaId(
|
class GetChaptersByMangaId(
|
||||||
private val chapterRepository: ChapterRepository,
|
private val chapterRepository: ChapterRepository,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -18,6 +18,16 @@ data class Chapter(
|
||||||
val isRecognizedNumber: Boolean
|
val isRecognizedNumber: Boolean
|
||||||
get() = chapterNumber >= 0f
|
get() = chapterNumber >= 0f
|
||||||
|
|
||||||
|
fun copyFrom(other: Chapter): Chapter {
|
||||||
|
return copy(
|
||||||
|
name = other.name,
|
||||||
|
url = other.url,
|
||||||
|
dateUpload = other.dateUpload,
|
||||||
|
chapterNumber = other.chapterNumber,
|
||||||
|
scanlator = other.scanlator?.ifBlank { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create() = Chapter(
|
fun create() = Chapter(
|
||||||
id = -1,
|
id = -1,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
import tachiyomi.domain.items.episode.repository.EpisodeRepository
|
import tachiyomi.domain.items.episode.repository.EpisodeRepository
|
||||||
|
|
||||||
class GetEpisodeByAnimeId(
|
class GetEpisodesByAnimeId(
|
||||||
private val episodeRepository: EpisodeRepository,
|
private val episodeRepository: EpisodeRepository,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -19,6 +19,16 @@ data class Episode(
|
||||||
val isRecognizedNumber: Boolean
|
val isRecognizedNumber: Boolean
|
||||||
get() = episodeNumber >= 0f
|
get() = episodeNumber >= 0f
|
||||||
|
|
||||||
|
fun copyFrom(other: Episode): Episode {
|
||||||
|
return copy(
|
||||||
|
name = other.name,
|
||||||
|
url = other.url,
|
||||||
|
dateUpload = other.dateUpload,
|
||||||
|
episodeNumber = other.episodeNumber,
|
||||||
|
scanlator = other.scanlator?.ifBlank { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create() = Episode(
|
fun create() = Episode(
|
||||||
id = -1,
|
id = -1,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package tachiyomi.domain.library.service
|
package tachiyomi.domain.library.service
|
||||||
|
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
import tachiyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
|
@ -41,7 +42,7 @@ class LibraryPreferences(
|
||||||
AnimeLibrarySort.Serializer::deserialize,
|
AnimeLibrarySort.Serializer::deserialize,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun lastUpdatedTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
|
fun lastUpdatedTimestamp() = preferenceStore.getLong(Preference.appStateKey("library_update_last_timestamp"), 0L)
|
||||||
fun autoUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
|
fun autoUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
|
||||||
|
|
||||||
fun autoUpdateDeviceRestrictions() = preferenceStore.getStringSet(
|
fun autoUpdateDeviceRestrictions() = preferenceStore.getStringSet(
|
||||||
|
@ -196,8 +197,8 @@ class LibraryPreferences(
|
||||||
fun defaultAnimeCategory() = preferenceStore.getInt("default_anime_category", -1)
|
fun defaultAnimeCategory() = preferenceStore.getInt("default_anime_category", -1)
|
||||||
fun defaultMangaCategory() = preferenceStore.getInt("default_category", -1)
|
fun defaultMangaCategory() = preferenceStore.getInt("default_category", -1)
|
||||||
|
|
||||||
fun lastUsedAnimeCategory() = preferenceStore.getInt("last_used_anime_category", 0)
|
fun lastUsedAnimeCategory() = preferenceStore.getInt(Preference.appStateKey("last_used_anime_category"), 0)
|
||||||
fun lastUsedMangaCategory() = preferenceStore.getInt("last_used_category", 0)
|
fun lastUsedMangaCategory() = preferenceStore.getInt(Preference.appStateKey("last_used_category"), 0)
|
||||||
|
|
||||||
fun animeUpdateCategories() =
|
fun animeUpdateCategories() =
|
||||||
preferenceStore.getStringSet("animelib_update_categories", emptySet())
|
preferenceStore.getStringSet("animelib_update_categories", emptySet())
|
||||||
|
|
|
@ -30,7 +30,7 @@ paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pag
|
||||||
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.0"
|
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.0"
|
||||||
test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha01"
|
test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha01"
|
||||||
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01"
|
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01"
|
||||||
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha04"
|
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha05"
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
lifecycle = ["lifecycle-common", "lifecycle-process", "lifecycle-runtimektx"]
|
lifecycle = ["lifecycle-common", "lifecycle-process", "lifecycle-runtimektx"]
|
|
@ -17,6 +17,7 @@ flowreactivenetwork = "ru.beryukhov:flowreactivenetwork:1.0.4"
|
||||||
|
|
||||||
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp_version" }
|
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp_version" }
|
||||||
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp_version" }
|
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp_version" }
|
||||||
|
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp_version" }
|
||||||
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp_version" }
|
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp_version" }
|
||||||
okio = "com.squareup.okio:okio:3.6.0"
|
okio = "com.squareup.okio:okio:3.6.0"
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@ seeker = "io.github.2307vivek:seeker:1.1.1"
|
||||||
truetypeparser = "io.github.yubyf:truetypeparser-light:2.1.4"
|
truetypeparser = "io.github.yubyf:truetypeparser-light:2.1.4"
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-dnsoverhttps"]
|
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"]
|
||||||
js-engine = ["quickjs-android"]
|
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"]
|
||||||
|
|
|
@ -158,6 +158,8 @@
|
||||||
<string name="pref_show_next_episode_airing_time">Show next episode\'s airing time </string>
|
<string name="pref_show_next_episode_airing_time">Show next episode\'s airing time </string>
|
||||||
<string name="pref_backup_flags_summary">What information to include in the backup file</string>
|
<string name="pref_backup_flags_summary">What information to include in the backup file</string>
|
||||||
<string name="pref_clear_chapter_cache">Clear chapter and episode cache</string>
|
<string name="pref_clear_chapter_cache">Clear chapter and episode cache</string>
|
||||||
|
<string name="pref_anime_storage_usage">Anime Storage usage</string>
|
||||||
|
<string name="pref_manga_storage_usage">Manga Storage usage</string>
|
||||||
<string name="used_cache_both">Used by anime: %1$s, used by manga: %2$s</string>
|
<string name="used_cache_both">Used by anime: %1$s, used by manga: %2$s</string>
|
||||||
<string name="pref_auto_clear_chapter_cache">Clear episode/chapter cache on app launch</string>
|
<string name="pref_auto_clear_chapter_cache">Clear episode/chapter cache on app launch</string>
|
||||||
<string name="pref_clear_manga_database">Clear Manga database</string>
|
<string name="pref_clear_manga_database">Clear Manga database</string>
|
||||||
|
|
|
@ -66,6 +66,7 @@
|
||||||
<string name="action_sort_latest_chapter">Latest chapter</string>
|
<string name="action_sort_latest_chapter">Latest chapter</string>
|
||||||
<string name="action_sort_chapter_fetch_date">Chapter fetch date</string>
|
<string name="action_sort_chapter_fetch_date">Chapter fetch date</string>
|
||||||
<string name="action_sort_date_added">Date added</string>
|
<string name="action_sort_date_added">Date added</string>
|
||||||
|
<string name="action_sort_tracker_score">Tracker score</string>
|
||||||
<string name="action_sort_airing_time">Airing time</string>
|
<string name="action_sort_airing_time">Airing time</string>
|
||||||
<string name="action_search">Search</string>
|
<string name="action_search">Search</string>
|
||||||
<string name="action_search_hint">Search…</string>
|
<string name="action_search_hint">Search…</string>
|
||||||
|
@ -499,6 +500,11 @@
|
||||||
<string name="restoring_backup_error">Restoring backup failed</string>
|
<string name="restoring_backup_error">Restoring backup failed</string>
|
||||||
<string name="restoring_backup_canceled">Canceled restore</string>
|
<string name="restoring_backup_canceled">Canceled restore</string>
|
||||||
<string name="backup_info">You should keep copies of backups in other places as well.</string>
|
<string name="backup_info">You should keep copies of backups in other places as well.</string>
|
||||||
|
<string name="last_auto_backup_info">Last automatically backed up: %s</string>
|
||||||
|
<string name="label_data">Data</string>
|
||||||
|
<string name="used_cache">Used: %1$s</string>
|
||||||
|
<string name="cache_deleted">Cache cleared. %1$d files have been deleted</string>
|
||||||
|
<string name="cache_delete_error">Error occurred while clearing</string>
|
||||||
|
|
||||||
<!-- Sync section -->
|
<!-- Sync section -->
|
||||||
<string name="syncing_library">Syncing library</string>
|
<string name="syncing_library">Syncing library</string>
|
||||||
|
@ -514,10 +520,6 @@
|
||||||
<string name="pref_reset_user_agent_string">Reset default user agent string</string>
|
<string name="pref_reset_user_agent_string">Reset default user agent string</string>
|
||||||
<string name="requires_app_restart">Requires app restart to take effect</string>
|
<string name="requires_app_restart">Requires app restart to take effect</string>
|
||||||
<string name="cookies_cleared">Cookies cleared</string>
|
<string name="cookies_cleared">Cookies cleared</string>
|
||||||
<string name="label_data">Data</string>
|
|
||||||
<string name="used_cache">Used: %1$s</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="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="download_cache_invalidated">Downloads index invalidated</string>
|
||||||
<string name="pref_clear_database">Clear database</string>
|
<string name="pref_clear_database">Clear database</string>
|
||||||
|
@ -766,6 +768,7 @@
|
||||||
<string name="cant_open_last_read_chapter">Unable to open last read chapter</string>
|
<string name="cant_open_last_read_chapter">Unable to open last read chapter</string>
|
||||||
<string name="updates_last_update_info">Library last updated: %s</string>
|
<string name="updates_last_update_info">Library last updated: %s</string>
|
||||||
<string name="updates_last_update_info_just_now">Just now</string>
|
<string name="updates_last_update_info_just_now">Just now</string>
|
||||||
|
<string name="relative_time_span_never">Never</string>
|
||||||
|
|
||||||
<!-- History -->
|
<!-- History -->
|
||||||
<string name="recent_manga_time">Ch. %1$s - %2$s</string>
|
<string name="recent_manga_time">Ch. %1$s - %2$s</string>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.composed
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
@ -69,7 +70,11 @@ fun TabText(
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(text = text)
|
Text(
|
||||||
|
text = text,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
if (badgeCount != null) {
|
if (badgeCount != null) {
|
||||||
Pill(
|
Pill(
|
||||||
text = "$badgeCount",
|
text = "$badgeCount",
|
||||||
|
|
Loading…
Reference in a new issue