From afb88c9da36312784c8fb53593d64dfa09bb3c15 Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat, 8 Jul 2023 13:44:22 +0200 Subject: [PATCH] merge33 Last Commit Merged: https://github.com/tachiyomiorg/tachiyomi/commit/4d3e13b0d13a902f0755714e0f56d284acad5a5f --- README.md | 2 +- .../kanade/presentation/components/Banners.kt | 13 +- .../components/EntryDownloadDropdownMenu.kt | 2 - .../entries/EntryBottomActionMenu.kt | 1 - .../entries/EntryScreenConstants.kt | 1 - .../track/TrackInfoDialogSelector.kt | 4 +- .../track/anime/AnimeTrackInfoDialogHome.kt | 5 +- .../track/manga/MangaTrackInfoDialogHome.kt | 5 +- .../eu/kanade/presentation/util/Navigator.kt | 4 +- .../data/download/manga/MangaDownloadCache.kt | 248 ++++++++++++------ .../download/manga/MangaDownloadManager.kt | 2 +- .../data/download/manga/MangaDownloader.kt | 2 +- .../tachiyomi/data/track/TrackManager.kt | 16 +- .../tachiyomi/data/track/TrackService.kt | 3 +- .../tachiyomi/data/track/anilist/Anilist.kt | 28 +- .../tachiyomi/data/track/bangumi/Bangumi.kt | 20 +- .../tachiyomi/data/track/kavita/Kavita.kt | 13 +- .../tachiyomi/data/track/kitsu/Kitsu.kt | 24 +- .../tachiyomi/data/track/komga/Komga.kt | 13 +- .../data/track/mangaupdates/MangaUpdates.kt | 20 +- .../data/track/myanimelist/MyAnimeList.kt | 28 +- .../data/track/shikimori/Shikimori.kt | 22 +- .../tachiyomi/data/track/simkl/Simkl.kt | 20 +- .../tachiyomi/data/track/suwayomi/Suwayomi.kt | 16 +- .../ui/entries/anime/AnimeScreenModel.kt | 1 - .../anime/track/AnimeTrackInfoDialog.kt | 2 +- .../ui/entries/manga/MangaScreenModel.kt | 11 +- .../manga/track/MangaTrackInfoDialog.kt | 2 +- .../eu/kanade/tachiyomi/ui/home/HomeScreen.kt | 28 +- .../kanade/tachiyomi/ui/main/MainActivity.kt | 10 +- .../tachiyomi/util/view/ViewExtensions.kt | 18 -- gradle/androidx.versions.toml | 8 +- gradle/compose.versions.toml | 2 +- gradle/kotlinx.versions.toml | 5 +- gradle/wrapper/gradle-wrapper.properties | 2 +- i18n/src/main/res/values/strings-aniyomi.xml | 2 +- i18n/src/main/res/values/strings.xml | 1 - 37 files changed, 319 insertions(+), 285 deletions(-) diff --git a/README.md b/README.md index 6e4bb522a0..75bc9488f0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ | Build | Preview Release | Codefactor | Stable | Translate Aniyomi | Discord Server | |-------|-----------|-------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------|---------| -| ![CI](https://github.com/aniyomiorg/aniyomi/workflows/CI/badge.svg?branch=master&event=push) | [![latest preview build](https://img.shields.io/github/v/release/aniyomiorg/aniyomi-preview.svg?maxAge=3600&label=download)](https://github.com/aniyomiorg/aniyomi-preview/releases) | [![CodeFactor](https://www.codefactor.io/repository/github/aniyomiorg/aniyomi/badge)](https://www.codefactor.io/repository/github/aniyomiorg/aniyomi) | [![stable release](https://img.shields.io/github/release/aniyomiorg/aniyomi.svg?maxAge=3600&label=download)](https://github.com/aniyomiorg/aniyomi/releases) | [![Translation status](https://hosted.weblate.org/widgets/aniyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/aniyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/841701076242530374?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/F32UjdJZrR) | +| [![CI](https://github.com/aniyomiorg/aniyomi/actions/workflows/build_push.yml/badge.svg)] (https://github.com/aniyomiorg/aniyomi/actions/workflows/build_push.yml) | [![latest preview build](https://img.shields.io/github/v/release/aniyomiorg/aniyomi-preview.svg?maxAge=3600&label=download)](https://github.com/aniyomiorg/aniyomi-preview/releases) | [![CodeFactor](https://www.codefactor.io/repository/github/aniyomiorg/aniyomi/badge)](https://www.codefactor.io/repository/github/aniyomiorg/aniyomi) | [![stable release](https://img.shields.io/github/release/aniyomiorg/aniyomi.svg?maxAge=3600&label=download)](https://github.com/aniyomiorg/aniyomi/releases) | [![Translation status](https://hosted.weblate.org/widgets/aniyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/aniyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/841701076242530374?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/F32UjdJZrR) | # ![app icon](.github/readme-images/app-icon.png)Aniyomi diff --git a/app/src/main/java/eu/kanade/presentation/components/Banners.kt b/app/src/main/java/eu/kanade/presentation/components/Banners.kt index 5f288d7e27..14525e137b 100644 --- a/app/src/main/java/eu/kanade/presentation/components/Banners.kt +++ b/app/src/main/java/eu/kanade/presentation/components/Banners.kt @@ -69,8 +69,7 @@ fun AppStateBanners( val mainInsets = WindowInsets.statusBars val mainInsetsTop = mainInsets.getTop(density) SubcomposeLayout(modifier = modifier) { constraints -> - val indexingId = if (indexing) 0 else -1 - val indexingPlaceable = subcompose(indexingId) { + val indexingPlaceable = subcompose(0) { AnimatedVisibility( visible = indexing, enter = expandVertically(), @@ -83,8 +82,7 @@ fun AppStateBanners( }.fastMap { it.measure(constraints) } val indexingHeight = indexingPlaceable.fastMaxBy { it.height }?.height ?: 0 - val downloadedId = if (indexing) 1 else 0 - val downloadedOnlyPlaceable = subcompose(downloadedId) { + val downloadedOnlyPlaceable = subcompose(1) { AnimatedVisibility( visible = downloadedOnlyMode, enter = expandVertically(), @@ -98,12 +96,7 @@ fun AppStateBanners( }.fastMap { it.measure(constraints) } val downloadedOnlyHeight = downloadedOnlyPlaceable.fastMaxBy { it.height }?.height ?: 0 - val incognitoId = when { - indexing && downloadedOnlyMode -> 3 - indexing || downloadedOnlyMode -> 2 - else -> 1 - } - val incognitoPlaceable = subcompose(incognitoId) { + val incognitoPlaceable = subcompose(2) { AnimatedVisibility( visible = incognitoMode, enter = expandVertically(), diff --git a/app/src/main/java/eu/kanade/presentation/components/EntryDownloadDropdownMenu.kt b/app/src/main/java/eu/kanade/presentation/components/EntryDownloadDropdownMenu.kt index 37099032f1..d44a894e7d 100644 --- a/app/src/main/java/eu/kanade/presentation/components/EntryDownloadDropdownMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/components/EntryDownloadDropdownMenu.kt @@ -13,7 +13,6 @@ fun EntryDownloadDropdownMenu( expanded: Boolean, onDismissRequest: () -> Unit, onDownloadClicked: (DownloadAction) -> Unit, - includeDownloadAllOption: Boolean = true, isManga: Boolean, ) { DropdownMenu( @@ -28,7 +27,6 @@ fun EntryDownloadDropdownMenu( DownloadAction.NEXT_10_ITEMS to pluralStringResource(downloadAmount, 10, 10), DownloadAction.NEXT_25_ITEMS to pluralStringResource(downloadAmount, 25, 25), DownloadAction.UNVIEWED_ITEMS to stringResource(downloadUnviewed), - (DownloadAction.ALL_ITEMS to stringResource(R.string.download_all)).takeIf { includeDownloadAllOption }, ).map { (downloadAction, string) -> DropdownMenuItem( text = { Text(text = string) }, diff --git a/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt index 2557d7a1b7..bcd41f7321 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt @@ -325,7 +325,6 @@ fun LibraryBottomActionMenu( expanded = downloadExpanded, onDismissRequest = onDismissRequest, onDownloadClicked = onDownloadClicked, - includeDownloadAllOption = false, isManga = isManga, ) } diff --git a/app/src/main/java/eu/kanade/presentation/entries/EntryScreenConstants.kt b/app/src/main/java/eu/kanade/presentation/entries/EntryScreenConstants.kt index dc3fc542d1..4b13112b27 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/EntryScreenConstants.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/EntryScreenConstants.kt @@ -6,7 +6,6 @@ enum class DownloadAction { NEXT_10_ITEMS, NEXT_25_ITEMS, UNVIEWED_ITEMS, - ALL_ITEMS, } enum class EditCoverAction { diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt index a2e2aa0cec..b3e6c79b11 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt @@ -43,7 +43,7 @@ import tachiyomi.presentation.core.util.isScrolledToStart fun TrackStatusSelector( selection: Int, onSelectionChange: (Int) -> Unit, - selections: Map, + selections: Map, onConfirm: () -> Unit, onDismissRequest: () -> Unit, ) { @@ -71,7 +71,7 @@ fun TrackStatusSelector( onClick = null, ) Text( - text = value, + text = value?.let { stringResource(it) } ?: "", style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 24.dp), ) diff --git a/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt index 35eed666a4..f1fcaff7dd 100644 --- a/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt @@ -1,5 +1,6 @@ package eu.kanade.presentation.track.anime +import androidx.annotation.StringRes import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable @@ -114,7 +115,7 @@ fun AnimeTrackInfoDialogHome( private fun TrackInfoItem( title: String, service: TrackService, - status: String, + @StringRes status: Int?, onStatusClick: () -> Unit, episodes: String, onEpisodesClick: () -> Unit, @@ -176,7 +177,7 @@ private fun TrackInfoItem( Row(modifier = Modifier.height(IntrinsicSize.Min)) { TrackDetailsItem( modifier = Modifier.weight(1f), - text = status, + text = status?.let { stringResource(it) } ?: "", onClick = onStatusClick, ) VerticalDivider() diff --git a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt index 188bfff254..4859f01052 100644 --- a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt @@ -1,5 +1,6 @@ package eu.kanade.presentation.track.manga +import androidx.annotation.StringRes import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -125,7 +126,7 @@ fun MangaTrackInfoDialogHome( private fun TrackInfoItem( title: String, service: TrackService, - status: String, + @StringRes status: Int?, onStatusClick: () -> Unit, chapters: String, onChaptersClick: () -> Unit, @@ -187,7 +188,7 @@ private fun TrackInfoItem( Row(modifier = Modifier.height(IntrinsicSize.Min)) { TrackDetailsItem( modifier = Modifier.weight(1f), - text = status, + text = status?.let { stringResource(it) } ?: "", onClick = onStatusClick, ) VerticalDivider() diff --git a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt index eab459b14f..fe89516d64 100644 --- a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt +++ b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt @@ -3,7 +3,6 @@ package eu.kanade.presentation.util import androidx.compose.runtime.Composable import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.core.screen.uniqueScreenKey @@ -34,7 +33,7 @@ interface AssistContentScreen { } @Composable -fun DefaultNavigatorScreenTransition(navigator: Navigator, modifier: Modifier = Modifier) { +fun DefaultNavigatorScreenTransition(navigator: Navigator) { val slideDistance = rememberSlideDistance() ScreenTransition( navigator = navigator, @@ -44,6 +43,5 @@ fun DefaultNavigatorScreenTransition(navigator: Navigator, modifier: Modifier = slideDistance = slideDistance, ) }, - modifier = modifier, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadCache.kt index c6c84aee42..1733391c5d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadCache.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.data.download.manga +import android.app.Application import android.content.Context +import android.net.Uri import androidx.core.net.toUri import com.hippo.unifile.UniFile import eu.kanade.core.util.mapNotNullKeys @@ -14,6 +16,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.debounce @@ -23,7 +26,20 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.protobuf.ProtoBuf import logcat.LogPriority import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNonCancellable @@ -34,6 +50,7 @@ import tachiyomi.domain.items.chapter.model.Chapter import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.File import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds @@ -76,7 +93,11 @@ class MangaDownloadCache( .debounce(1000L) // Don't notify if it finishes quickly enough .stateIn(scope, SharingStarted.WhileSubscribed(), false) - private var rootDownloadsDir = RootDirectory(getDirectoryFromPreference()) + private val diskCacheFile: File + get() = File(context.cacheDir, "dl_index_cache") + + private val rootDownloadsDirLock = Mutex() + private var rootDownloadsDir: RootDirectory init { downloadPreferences.downloadsDirectory().changes() @@ -85,6 +106,21 @@ class MangaDownloadCache( invalidateCache() } .launchIn(scope) + + rootDownloadsDir = runBlocking(Dispatchers.IO) { + try { + val diskCache = diskCacheFile.inputStream().use { + ProtoBuf.decodeFromByteArray(it.readBytes()) + } + lastRenew = 1 // Just so that the banner won't show up + diskCache + } catch (e: Throwable) { + diskCacheFile.delete() + null + } + } ?: RootDirectory(getDirectoryFromPreference()) + + notifyChanges() } /** @@ -158,28 +194,28 @@ class MangaDownloadCache( * @param mangaUniFile the directory of the manga. * @param manga the manga of the chapter. */ - @Synchronized - fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) { - // Retrieve the cached source directory or cache a new one - var sourceDir = rootDownloadsDir.sourceDirs[manga.source] - if (sourceDir == null) { - val source = sourceManager.get(manga.source) ?: return - val sourceUniFile = provider.findSourceDir(source) ?: return - sourceDir = SourceDirectory(sourceUniFile) - rootDownloadsDir.sourceDirs += manga.source to sourceDir - } - - // Retrieve the cached manga directory or cache a new one - val mangaDirName = provider.getMangaDirName(manga.title) - var mangaDir = sourceDir.mangaDirs[mangaDirName] - if (mangaDir == null) { - mangaDir = MangaDirectory(mangaUniFile) - sourceDir.mangaDirs += mangaDirName to mangaDir - } + suspend fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) { + rootDownloadsDirLock.withLock { + // Retrieve the cached source directory or cache a new one + var sourceDir = rootDownloadsDir.sourceDirs[manga.source] + if (sourceDir == null) { + val source = sourceManager.get(manga.source) ?: return + val sourceUniFile = provider.findSourceDir(source) ?: return + sourceDir = SourceDirectory(sourceUniFile) + rootDownloadsDir.sourceDirs += manga.source to sourceDir + } - // Save the chapter directory - mangaDir.chapterDirs += chapterDirName + // Retrieve the cached manga directory or cache a new one + val mangaDirName = provider.getMangaDirName(manga.title) + var mangaDir = sourceDir.mangaDirs[mangaDirName] + if (mangaDir == null) { + mangaDir = MangaDirectory(mangaUniFile) + sourceDir.mangaDirs += mangaDirName to mangaDir + } + // Save the chapter directory + mangaDir.chapterDirs += chapterDirName + } notifyChanges() } @@ -189,13 +225,14 @@ class MangaDownloadCache( * @param chapter the chapter to remove. * @param manga the manga of the chapter. */ - @Synchronized - fun removeChapter(chapter: Chapter, manga: Manga) { - val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return - val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return - provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { - if (it in mangaDir.chapterDirs) { - mangaDir.chapterDirs -= it + suspend fun removeChapter(chapter: Chapter, manga: Manga) { + rootDownloadsDirLock.withLock { + val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return + val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return + provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { + if (it in mangaDir.chapterDirs) { + mangaDir.chapterDirs -= it + } } } @@ -208,14 +245,16 @@ class MangaDownloadCache( * @param chapters the list of chapter to remove. * @param manga the manga of the chapter. */ - @Synchronized - fun removeChapters(chapters: List, manga: Manga) { - val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return - val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return - chapters.forEach { chapter -> - provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { - if (it in mangaDir.chapterDirs) { - mangaDir.chapterDirs -= it + + suspend fun removeChapters(chapters: List, manga: Manga) { + rootDownloadsDirLock.withLock { + val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return + val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return + chapters.forEach { chapter -> + provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { + if (it in mangaDir.chapterDirs) { + mangaDir.chapterDirs -= it + } } } } @@ -228,20 +267,22 @@ class MangaDownloadCache( * * @param manga the manga to remove. */ - @Synchronized - fun removeManga(manga: Manga) { - val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return - val mangaDirName = provider.getMangaDirName(manga.title) - if (sourceDir.mangaDirs.containsKey(mangaDirName)) { - sourceDir.mangaDirs -= mangaDirName + suspend fun removeManga(manga: Manga) { + rootDownloadsDirLock.withLock { + val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return + val mangaDirName = provider.getMangaDirName(manga.title) + if (sourceDir.mangaDirs.containsKey(mangaDirName)) { + sourceDir.mangaDirs -= mangaDirName + } } notifyChanges() } - @Synchronized - fun removeSource(source: MangaSource) { - rootDownloadsDir.sourceDirs -= source.id + suspend fun removeSource(source: MangaSource) { + rootDownloadsDirLock.withLock { + rootDownloadsDir.sourceDirs -= source.id + } notifyChanges() } @@ -287,46 +328,50 @@ class MangaDownloadCache( } } - val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty() - .associate { it.name to SourceDirectory(it) } - .mapNotNullKeys { entry -> - sources.find { - provider.getSourceDirName(it).equals(entry.key, ignoreCase = true) - }?.id - } + rootDownloadsDirLock.withLock { + val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty() + .associate { it.name to SourceDirectory(it) } + .mapNotNullKeys { entry -> + sources.find { + provider.getSourceDirName(it).equals(entry.key, ignoreCase = true) + }?.id + } - rootDownloadsDir.sourceDirs = sourceDirs - - sourceDirs.values - .map { sourceDir -> - async { - val mangaDirs = sourceDir.dir.listFiles().orEmpty() - .filterNot { it.name.isNullOrBlank() } - .associate { it.name!! to MangaDirectory(it) } - - sourceDir.mangaDirs = ConcurrentHashMap(mangaDirs) - - mangaDirs.values.forEach { mangaDir -> - val chapterDirs = mangaDir.dir.listFiles().orEmpty() - .mapNotNull { - when { - // Ignore incomplete downloads - it.name?.endsWith(MangaDownloader.TMP_DIR_SUFFIX) == true -> null - // Folder of images - it.isDirectory -> it.name - // CBZ files - it.isFile && it.name?.endsWith(".cbz") == true -> it.name!!.substringBeforeLast(".cbz") - // Anything else is irrelevant - else -> null + rootDownloadsDir.sourceDirs = sourceDirs + + sourceDirs.values + .map { sourceDir -> + async { + val mangaDirs = sourceDir.dir.listFiles().orEmpty() + .filterNot { it.name.isNullOrBlank() } + .associate { it.name!! to MangaDirectory(it) } + + sourceDir.mangaDirs = ConcurrentHashMap(mangaDirs) + + mangaDirs.values.forEach { mangaDir -> + val chapterDirs = mangaDir.dir.listFiles().orEmpty() + .mapNotNull { + when { + // Ignore incomplete downloads + it.name?.endsWith(MangaDownloader.TMP_DIR_SUFFIX) == true -> null + // Folder of images + it.isDirectory -> it.name + // CBZ files + it.isFile && it.name?.endsWith(".cbz") == true -> it.name!!.substringBeforeLast( + ".cbz", + ) + // Anything else is irrelevant + else -> null + } } - } - .toMutableSet() + .toMutableSet() - mangaDir.chapterDirs = chapterDirs + mangaDir.chapterDirs = chapterDirs + } } } - } - .awaitAll() + .awaitAll() + } _isInitializing.emit(false) }.also { @@ -335,6 +380,7 @@ class MangaDownloadCache( logcat(LogPriority.ERROR, exception) { "Failed to create download cache" } } lastRenew = System.currentTimeMillis() + notifyChanges() } } @@ -351,29 +397,67 @@ class MangaDownloadCache( scope.launchNonCancellable { _changes.send(Unit) } + updateDiskCache() + } + + private var updateDiskCacheJob: Job? = null + private fun updateDiskCache() { + updateDiskCacheJob?.cancel() + updateDiskCacheJob = scope.launchIO { + delay(1000) + ensureActive() + val bytes = ProtoBuf.encodeToByteArray(rootDownloadsDir) + ensureActive() + try { + diskCacheFile.writeBytes(bytes) + } catch (e: Throwable) { + logcat( + priority = LogPriority.ERROR, + throwable = e, + message = { "Failed to write disk cache file" }, + ) + } + } } } /** * Class to store the files under the root downloads directory. */ +@Serializable private class RootDirectory( + @Serializable(with = UniFileAsStringSerializer::class) val dir: UniFile, - var sourceDirs: ConcurrentHashMap = ConcurrentHashMap(), + var sourceDirs: Map = mapOf(), ) /** * Class to store the files under a source directory. */ +@Serializable private class SourceDirectory( + @Serializable(with = UniFileAsStringSerializer::class) val dir: UniFile, - var mangaDirs: ConcurrentHashMap = ConcurrentHashMap(), + var mangaDirs: Map = mapOf(), ) /** * Class to store the files under a manga directory. */ +@Serializable private class MangaDirectory( + @Serializable(with = UniFileAsStringSerializer::class) val dir: UniFile, var chapterDirs: MutableSet = mutableSetOf(), ) + +private object UniFileAsStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UniFile", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: UniFile) { + return encoder.encodeString(value.uri.toString()) + } + override fun deserialize(decoder: Decoder): UniFile { + return UniFile.fromUri(Injekt.get(), Uri.parse(decoder.decodeString())) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt index 80a239afe1..1ec8689ebb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt @@ -324,7 +324,7 @@ class MangaDownloadManager( * @param oldChapter the existing chapter with the old name. * @param newChapter the target chapter with the new name. */ - fun renameChapter(source: MangaSource, manga: Manga, oldChapter: Chapter, newChapter: Chapter) { + suspend fun renameChapter(source: MangaSource, manga: Manga, oldChapter: Chapter, newChapter: Chapter) { val oldNames = provider.getValidChapterDirNames(oldChapter.name, oldChapter.scanlator) val mangaDir = provider.getMangaDir(manga.title, source) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt index 2cdf08e9d7..39b7c6e9a1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt @@ -527,7 +527,7 @@ class MangaDownloader( * @param tmpDir the directory where the download is currently stored. * @param dirname the real (non temporary) directory name of the download. */ - private fun ensureSuccessfulDownload( + private suspend fun ensureSuccessfulDownload( download: MangaDownload, mangaDir: UniFile, tmpDir: UniFile, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt index 21d75a72b1..27a75bac15 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt @@ -27,16 +27,16 @@ class TrackManager(context: Context) { const val SIMKL = 101L } - val myAnimeList = MyAnimeList(context, MYANIMELIST) - val aniList = Anilist(context, ANILIST) - val kitsu = Kitsu(context, KITSU) - val shikimori = Shikimori(context, SHIKIMORI) - val bangumi = Bangumi(context, BANGUMI) + val myAnimeList = MyAnimeList(MYANIMELIST) + val aniList = Anilist(ANILIST) + val kitsu = Kitsu(KITSU) + val shikimori = Shikimori(SHIKIMORI) + val bangumi = Bangumi(BANGUMI) val komga = Komga(context, KOMGA) - val mangaUpdates = MangaUpdates(context, MANGA_UPDATES) + val mangaUpdates = MangaUpdates(MANGA_UPDATES) val kavita = Kavita(context, KAVITA) - val suwayomi = Suwayomi(context, SUWAYOMI) - val simkl = Simkl(context, SIMKL) + val suwayomi = Suwayomi(SUWAYOMI) + val simkl = Simkl(SIMKL) val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates, kavita, suwayomi, simkl) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt index 9ef5faf8eb..e8add8ccb4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt @@ -32,7 +32,8 @@ abstract class TrackService(val id: Long) { @ColorInt abstract fun getLogoColor(): Int - abstract fun getStatus(status: Int): String + @StringRes + abstract fun getStatus(status: Int): Int? abstract suspend fun login(username: String, password: String) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt index dbd73b3cef..66e4ffc38b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.anilist -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -18,7 +17,7 @@ import uy.kohesive.injekt.injectLazy import tachiyomi.domain.track.anime.model.AnimeTrack as DomainAnimeTrack import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack -class Anilist(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class Anilist(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { companion object { const val READING = 1 @@ -73,19 +72,18 @@ class Anilist(private val context: Context, id: Long) : TrackService(id), MangaT return listOf(WATCHING, PLANNING_ANIME, COMPLETED, REPEATING_ANIME, PAUSED, DROPPED) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - WATCHING -> getString(R.string.watching) - READING -> getString(R.string.reading) - PLANNING -> getString(R.string.plan_to_read) - PLANNING_ANIME -> getString(R.string.plan_to_watch) - COMPLETED -> getString(R.string.completed) - REPEATING -> getString(R.string.repeating) - REPEATING_ANIME -> getString(R.string.repeating_anime) - PAUSED -> getString(R.string.paused) - DROPPED -> getString(R.string.dropped) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + WATCHING -> R.string.watching + READING -> R.string.reading + PLANNING -> R.string.plan_to_read + PLANNING_ANIME -> R.string.plan_to_watch + COMPLETED -> R.string.completed + REPEATING -> R.string.repeating + REPEATING_ANIME -> R.string.repeating_anime + PAUSED -> R.string.paused + DROPPED -> R.string.dropped + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt index c00441f0b6..6f267d0b9f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.bangumi -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -16,7 +15,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class Bangumi(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class Bangumi(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { private val json: Json by injectLazy() @@ -166,15 +165,14 @@ class Bangumi(private val context: Context, id: Long) : TrackService(id), MangaT return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.reading) - PLAN_TO_READ -> getString(R.string.plan_to_read) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + READING -> R.string.reading + PLAN_TO_READ -> R.string.plan_to_read + COMPLETED -> R.string.completed + ON_HOLD -> R.string.on_hold + DROPPED -> R.string.dropped + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt index c6d91eed97..616b7f217a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt @@ -37,13 +37,12 @@ class Kavita(private val context: Context, id: Long) : TrackService(id), Enhance override fun getStatusListManga() = listOf(UNREAD, READING, COMPLETED) - override fun getStatus(status: Int): String = with(context) { - when (status) { - UNREAD -> getString(R.string.unread) - READING -> getString(R.string.reading) - COMPLETED -> getString(R.string.completed) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + UNREAD -> R.string.unread + READING -> R.string.reading + COMPLETED -> R.string.completed + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt index 8fb84a1750..f1b395470b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.kitsu -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -17,7 +16,7 @@ import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat -class Kitsu(private val context: Context, id: Long) : TrackService(id), AnimeTrackService, MangaTrackService { +class Kitsu(id: Long) : TrackService(id), AnimeTrackService, MangaTrackService { companion object { const val READING = 1 @@ -52,17 +51,16 @@ class Kitsu(private val context: Context, id: Long) : TrackService(id), AnimeTra return listOf(WATCHING, PLAN_TO_WATCH, COMPLETED, ON_HOLD, DROPPED) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.currently_reading) - WATCHING -> getString(R.string.currently_watching) - PLAN_TO_READ -> getString(R.string.want_to_read) - PLAN_TO_WATCH -> getString(R.string.want_to_watch) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + READING -> R.string.currently_reading + WATCHING -> R.string.currently_watching + PLAN_TO_READ -> R.string.want_to_read + PLAN_TO_WATCH -> R.string.want_to_watch + COMPLETED -> R.string.completed + ON_HOLD -> R.string.on_hold + DROPPED -> R.string.dropped + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt index 3e2352f896..040f319813 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt @@ -39,13 +39,12 @@ class Komga(private val context: Context, id: Long) : TrackService(id), Enhanced override fun getStatusListManga() = listOf(UNREAD, READING, COMPLETED) - override fun getStatus(status: Int): String = with(context) { - when (status) { - UNREAD -> getString(R.string.unread) - READING -> getString(R.string.reading) - COMPLETED -> getString(R.string.completed) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + UNREAD -> R.string.unread + READING -> R.string.reading + COMPLETED -> R.string.completed + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt index ae7ece0b60..0db8baf025 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.mangaupdates -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -11,7 +10,7 @@ import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch -class MangaUpdates(private val context: Context, id: Long) : TrackService(id), MangaTrackService { +class MangaUpdates(id: Long) : TrackService(id), MangaTrackService { companion object { const val READING_LIST = 0 @@ -36,15 +35,14 @@ class MangaUpdates(private val context: Context, id: Long) : TrackService(id), M return listOf(READING_LIST, COMPLETE_LIST, ON_HOLD_LIST, UNFINISHED_LIST, WISH_LIST) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING_LIST -> getString(R.string.reading_list) - WISH_LIST -> getString(R.string.wish_list) - COMPLETE_LIST -> getString(R.string.complete_list) - ON_HOLD_LIST -> getString(R.string.on_hold_list) - UNFINISHED_LIST -> getString(R.string.unfinished_list) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + READING_LIST -> R.string.reading_list + WISH_LIST -> R.string.wish_list + COMPLETE_LIST -> R.string.complete_list + ON_HOLD_LIST -> R.string.on_hold_list + UNFINISHED_LIST -> R.string.unfinished_list + else -> null } override fun getReadingStatus(): Int = READING_LIST diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt index 48a8ff9513..8aa555ab3f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.myanimelist -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -16,7 +15,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class MyAnimeList(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class MyAnimeList(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { companion object { const val READING = 1 @@ -55,19 +54,18 @@ class MyAnimeList(private val context: Context, id: Long) : TrackService(id), Ma return listOf(WATCHING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_WATCH, REWATCHING) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.reading) - WATCHING -> getString(R.string.watching) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - PLAN_TO_READ -> getString(R.string.plan_to_read) - PLAN_TO_WATCH -> getString(R.string.plan_to_watch) - REREADING -> getString(R.string.repeating) - REWATCHING -> getString(R.string.repeating_anime) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + READING -> R.string.reading + WATCHING -> R.string.watching + COMPLETED -> R.string.completed + ON_HOLD -> R.string.on_hold + DROPPED -> R.string.dropped + PLAN_TO_READ -> R.string.plan_to_read + PLAN_TO_WATCH -> R.string.plan_to_watch + REREADING -> R.string.repeating + REWATCHING -> R.string.repeating_anime + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt index 2e95511234..5a91a59990 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.shikimori -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -16,7 +15,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class Shikimori(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class Shikimori(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { companion object { const val READING = 1 @@ -164,16 +163,15 @@ class Shikimori(private val context: Context, id: Long) : TrackService(id), Mang return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.reading) - PLAN_TO_READ -> getString(R.string.plan_to_read) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - REREADING -> getString(R.string.repeating) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + READING -> R.string.reading + PLAN_TO_READ -> R.string.plan_to_read + COMPLETED -> R.string.completed + ON_HOLD -> R.string.on_hold + DROPPED -> R.string.dropped + REREADING -> R.string.repeating + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/Simkl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/Simkl.kt index 33218d4e84..bf7cd854f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/Simkl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/Simkl.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.simkl -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -13,7 +12,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class Simkl(private val context: Context, id: Long) : TrackService(id), AnimeTrackService { +class Simkl(id: Long) : TrackService(id), AnimeTrackService { companion object { const val WATCHING = 1 @@ -99,15 +98,14 @@ class Simkl(private val context: Context, id: Long) : TrackService(id), AnimeTra return listOf(WATCHING, COMPLETED, ON_HOLD, NOT_INTERESTING, PLAN_TO_WATCH) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - WATCHING -> getString(R.string.watching) - PLAN_TO_WATCH -> getString(R.string.plan_to_watch) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - NOT_INTERESTING -> getString(R.string.not_interesting) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + WATCHING -> R.string.watching + PLAN_TO_WATCH -> R.string.plan_to_watch + COMPLETED -> R.string.completed + ON_HOLD -> R.string.on_hold + NOT_INTERESTING -> R.string.not_interesting + else -> null } override fun getWatchingStatus(): Int = WATCHING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt index 69f8b9b6d4..28d05a89e2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.suwayomi -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -13,7 +12,7 @@ import eu.kanade.tachiyomi.source.MangaSource import tachiyomi.domain.entries.manga.model.Manga as DomainManga import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack -class Suwayomi(private val context: Context, id: Long) : TrackService(id), EnhancedMangaTrackService, MangaTrackService { +class Suwayomi(id: Long) : TrackService(id), EnhancedMangaTrackService, MangaTrackService { val api by lazy { TachideskApi() } @StringRes @@ -31,13 +30,12 @@ class Suwayomi(private val context: Context, id: Long) : TrackService(id), Enhan override fun getStatusListManga() = listOf(UNREAD, READING, COMPLETED) - override fun getStatus(status: Int): String = with(context) { - when (status) { - UNREAD -> getString(R.string.unread) - READING -> getString(R.string.reading) - COMPLETED -> getString(R.string.completed) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + UNREAD -> R.string.unread + READING -> R.string.reading + COMPLETED -> R.string.completed + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt index a8555f8940..6100706524 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt @@ -629,7 +629,6 @@ class AnimeInfoScreenModel( DownloadAction.NEXT_25_ITEMS -> getUnseenEpisodesSorted().take(25) DownloadAction.UNVIEWED_ITEMS -> getUnseenEpisodes() - DownloadAction.ALL_ITEMS -> successState?.episodes?.map { it.episode } } if (!episodesToDownload.isNullOrEmpty()) { startDownload(episodesToDownload, false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt index 57e9b0bdbe..723d553c81 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt @@ -293,7 +293,7 @@ private data class TrackStatusSelectorScreen( private val service: TrackService, ) : StateScreenModel(State(track.status)) { - fun getSelections(): Map { + fun getSelections(): Map { return service.animeService.getStatusListAnime().associateWith { service.getStatus(it) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt index c6362dfefb..94fb6867d0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt @@ -542,13 +542,6 @@ class MangaInfoScreenModel( } } - /** - * Returns the list of filtered or all chapter items if [skipFiltered] is false. - */ - private fun getChapterItems(): List { - return if (skipFiltered) filteredChapters.orEmpty().toList() else allChapters.orEmpty() - } - /** * Returns the next unread chapter or null if everything is read. */ @@ -558,7 +551,8 @@ class MangaInfoScreenModel( } private fun getUnreadChapters(): List { - return getChapterItems() + val chapterItems = if (skipFiltered) filteredChapters.orEmpty().toList() else allChapters.orEmpty() + return chapterItems .filter { (chapter, dlStatus) -> !chapter.read && dlStatus == MangaDownload.State.NOT_DOWNLOADED } .map { it.chapter } } @@ -631,7 +625,6 @@ class MangaInfoScreenModel( DownloadAction.NEXT_25_ITEMS -> getUnreadChaptersSorted().take(25) DownloadAction.UNVIEWED_ITEMS -> getUnreadChapters() - DownloadAction.ALL_ITEMS -> getChapterItems().map { it.chapter } } if (!chaptersToDownload.isNotEmpty()) { startDownload(chaptersToDownload, false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt index 4ff97be297..e32b9280b8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt @@ -293,7 +293,7 @@ private data class TrackStatusSelectorScreen( private val service: TrackService, ) : StateScreenModel(State(track.status)) { - fun getSelections(): Map { + fun getSelections(): Map { return service.mangaService.getStatusListManga().associateWith { service.getStatus(it) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt index e59cdacb70..00adce0245 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically import androidx.compose.animation.with +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.consumeWindowInsets @@ -145,21 +146,24 @@ object HomeScreen : Screen() { }, contentWindowInsets = WindowInsets(0), ) { contentPadding -> - AnimatedContent( + Box( modifier = Modifier .padding(contentPadding) .consumeWindowInsets(contentPadding), - targetState = tabNavigator.current, - transitionSpec = { - materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) with - materialFadeThroughOut(durationMillis = TabFadeDuration) - }, - content = { - tabNavigator.saveableState(key = "currentTab", it) { - it.Content() - } - }, - ) + ) { + AnimatedContent( + targetState = tabNavigator.current, + transitionSpec = { + materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) with + materialFadeThroughOut(durationMillis = TabFadeDuration) + }, + content = { + tabNavigator.saveableState(key = "currentTab", it) { + it.Content() + } + }, + ) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 2e1d05e500..ac660b0307 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -13,6 +13,7 @@ import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.BackHandler import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.consumeWindowInsets @@ -237,14 +238,15 @@ class MainActivity : BaseActivity() { }, contentWindowInsets = scaffoldInsets, ) { contentPadding -> - // Shows current screen // Consume insets already used by app state banners - DefaultNavigatorScreenTransition( - navigator = navigator, + Box( modifier = Modifier .padding(contentPadding) .consumeWindowInsets(contentPadding), - ) + ) { + // Shows current screen + DefaultNavigatorScreenTransition(navigator = navigator) + } } // Pop source-related screens when incognito mode is turned off diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 14b0f76d39..92f21074d6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -19,36 +19,18 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.TooltipCompat -import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionContext import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.view.forEach import com.google.android.material.shape.MaterialShapeDrawable import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.getResourceColor -inline fun ComposeView.setComposeContent(crossinline content: @Composable () -> Unit) { - consumeWindowInsets = false - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - TachiyomiTheme { - CompositionLocalProvider( - LocalTextStyle provides MaterialTheme.typography.bodySmall, - LocalContentColor provides MaterialTheme.colorScheme.onBackground, - ) { - content() - } - } - } -} - inline fun ComponentActivity.setComposeContent( parent: CompositionContext? = null, crossinline content: @Composable () -> Unit, diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index e0e3c5582c..de27923dd6 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -1,6 +1,6 @@ [versions] -agp_version = "7.4.1" -lifecycle_version = "2.6.0-rc01" +agp_version = "7.4.2" +lifecycle_version = "2.6.0" [libraries] gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" } @@ -10,9 +10,9 @@ appcompat = "androidx.appcompat:appcompat:1.6.1" biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05" constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:1.2.0" -corektx = "androidx.core:core-ktx:1.10.0-beta01" +corektx = "androidx.core:core-ktx:1.10.0-rc01" splashscreen = "androidx.core:core-splashscreen:1.0.0-alpha02" -recyclerview = "androidx.recyclerview:recyclerview:1.3.0-rc01" +recyclerview = "androidx.recyclerview:recyclerview:1.3.0" viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01" glance = "androidx.glance:glance-appwidget:1.0.0-alpha03" profileinstaller = "androidx.profileinstaller:profileinstaller:1.2.2" diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index bf25263d35..a18f82e436 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,6 +1,6 @@ [versions] compiler = "1.4.3" -compose-bom = "2023.02.00-beta02" +compose-bom = "2023.02.00-rc02" accompanist = "0.29.1-alpha" [libraries] diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index ba522ddd34..d462eb7069 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -1,8 +1,7 @@ [versions] kotlin_version = "1.8.10" -# TODO: 1.4.1 introduces an issue with cached serializers; see https://github.com/Kotlin/kotlinx.serialization/issues/2065 -serialization_version = "1.4.0" -xml_serialization_version = "0.84.3" +serialization_version = "1.5.0" +xml_serialization_version = "0.85.0" [libraries] reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin_version" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fc10b601f7..bdc9a83b1e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/i18n/src/main/res/values/strings-aniyomi.xml b/i18n/src/main/res/values/strings-aniyomi.xml index 3cc201f409..15bf183ee6 100644 --- a/i18n/src/main/res/values/strings-aniyomi.xml +++ b/i18n/src/main/res/values/strings-aniyomi.xml @@ -176,7 +176,7 @@ By episode number Next episode - Next episodes + Next %d episodes Unseen Are you sure you want to delete the selected episodes? diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 55282396e5..2dfec4c666 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -643,7 +643,6 @@ Next chapter Next %d chapters - All Unread Custom cover Cover