diff --git a/app/src/main/java/eu/kanade/presentation/more/storage/CumulativeStorage.kt b/app/src/main/java/eu/kanade/presentation/more/storage/CumulativeStorage.kt index 40f51001ff..4f590478b3 100644 --- a/app/src/main/java/eu/kanade/presentation/more/storage/CumulativeStorage.kt +++ b/app/src/main/java/eu/kanade/presentation/more/storage/CumulativeStorage.kt @@ -31,15 +31,14 @@ fun CumulativeStorage( Canvas( modifier = Modifier.aspectRatio(1f), onDraw = { + // don't bother with drawing anything if there's no data + if (totalSize == 0f) return@Canvas + val totalAngle = 180f var currentAngle = 0f rotate(180f) { for (item in items) { - val itemAngle = if (totalSize > 0f) { - (item.size / totalSize) * totalAngle - } else { - 0f - } + val itemAngle = (item.size / totalSize) * totalAngle drawArc( color = item.color, startAngle = currentAngle, diff --git a/app/src/main/java/eu/kanade/presentation/more/storage/StorageItem.kt b/app/src/main/java/eu/kanade/presentation/more/storage/StorageItem.kt index 2ffb127bd1..c9938ba728 100644 --- a/app/src/main/java/eu/kanade/presentation/more/storage/StorageItem.kt +++ b/app/src/main/java/eu/kanade/presentation/more/storage/StorageItem.kt @@ -45,6 +45,7 @@ data class StorageItem( val thumbnail: String?, val entriesCount: Int, val color: Color, + val showDeleteButton: Boolean, ) @Composable @@ -110,17 +111,19 @@ fun StorageItem( ) }, ) - IconButton( - onClick = { - showDeleteDialog = true - }, - content = { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = stringResource(R.string.action_delete), - ) - }, - ) + if (item.showDeleteButton) { + IconButton( + onClick = { + showDeleteDialog = true + }, + content = { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = stringResource(R.string.action_delete), + ) + }, + ) + } }, ) @@ -190,6 +193,7 @@ private fun StorageItemPreview() { thumbnail = null, entriesCount = 123, color = Color.Red, + showDeleteButton = true, ), isManga = true, onDelete = { diff --git a/app/src/main/java/eu/kanade/presentation/more/storage/StorageScreenContent.kt b/app/src/main/java/eu/kanade/presentation/more/storage/StorageScreenContent.kt index 0df61c617c..5722d11c0d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/storage/StorageScreenContent.kt +++ b/app/src/main/java/eu/kanade/presentation/more/storage/StorageScreenContent.kt @@ -142,6 +142,7 @@ private fun StorageScreenContentPreview() { random.nextInt(255), random.nextInt(255), ), + showDeleteButton = true, ) }, categories = categories, @@ -183,6 +184,7 @@ private fun StorageTabletUiScreenContentPreview() { random.nextInt(255), random.nextInt(255), ), + showDeleteButton = true, ) }, categories = categories, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt index dc35e58d8c..189c7a799a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt @@ -254,6 +254,22 @@ class AnimeDownloadManager( } } + /** + * Returns the size of downloaded episodes. + */ + fun getDownloadSize(): Long { + return cache.getTotalDownloadSize() + } + + /** + * Returns the size of downloaded episodes for an anime. + * + * @param anime the anime to check. + */ + fun getDownloadSize(anime: Anime): Long { + return cache.getDownloadSize(anime) + } + fun cancelQueuedDownloads(downloads: List) { removeFromDownloadQueue(downloads.map { it.episode }) } 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 b5eb1e3cfa..0f4bff0de6 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 @@ -233,6 +233,22 @@ class MangaDownloadManager( } } + /** + * Returns the size of downloaded chapters. + */ + fun getDownloadSize(): Long { + return cache.getTotalDownloadSize() + } + + /** + * Returns the size of downloaded chapters for a manga. + * + * @param manga the manga to check. + */ + fun getDownloadSize(manga: Manga): Long { + return cache.getDownloadSize(manga) + } + fun cancelQueuedDownloads(downloads: List) { removeFromDownloadQueue(downloads.map { it.chapter }) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/storage/CommonStorageScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/storage/CommonStorageScreenModel.kt index e6c3af31d5..9a76067b26 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/storage/CommonStorageScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/storage/CommonStorageScreenModel.kt @@ -22,12 +22,14 @@ abstract class CommonStorageScreenModel( private val downloadCacheIsInitializing: StateFlow, private val libraries: Flow>, private val categories: Flow>, + private val getTotalDownloadSize: () -> Long, private val getDownloadSize: T.() -> Long, private val getDownloadCount: T.() -> Int, private val getId: T.() -> Long, private val getCategoryId: T.() -> Long, private val getTitle: T.() -> String, private val getThumbnail: T.() -> String?, + private val isFromLocalSource: T.() -> Boolean, ) : StateScreenModel(StorageScreenState.Loading) { private val selectedCategory = MutableStateFlow(AllCategory) @@ -46,13 +48,14 @@ abstract class CommonStorageScreenModel( }.filter { selectedCategory == AllCategory || it.getCategoryId() == selectedCategory.id } + val size = getTotalDownloadSize() + val random = Random(size + distinctLibraries.size) mutableState.update { StorageScreenState.Success( selectedCategory = selectedCategory, categories = listOf(AllCategory, *categories.toTypedArray()), items = distinctLibraries.map { - val random = Random(it.getId()) StorageItem( id = it.getId(), title = it.getTitle(), @@ -64,6 +67,7 @@ abstract class CommonStorageScreenModel( random.nextInt(255), random.nextInt(255), ), + showDeleteButton = !it.isFromLocalSource(), ) }.sortedByDescending { it.size }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/storage/anime/AnimeStorageScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/storage/anime/AnimeStorageScreenModel.kt index 80ab6206eb..d7140e2b85 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/storage/anime/AnimeStorageScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/storage/anime/AnimeStorageScreenModel.kt @@ -1,14 +1,18 @@ package eu.kanade.tachiyomi.ui.storage.anime import cafe.adriel.voyager.core.model.coroutineScope +import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadCache import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager import eu.kanade.tachiyomi.ui.storage.CommonStorageScreenModel +import eu.kanade.tachiyomi.util.size import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.domain.category.anime.interactor.GetVisibleAnimeCategories import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime import tachiyomi.domain.library.anime.LibraryAnime import tachiyomi.domain.source.anime.service.AnimeSourceManager +import tachiyomi.source.local.entries.anime.isLocal +import tachiyomi.source.local.io.anime.LocalAnimeSourceFileSystem import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -18,17 +22,34 @@ class AnimeStorageScreenModel( getVisibleCategories: GetVisibleAnimeCategories = Injekt.get(), private val downloadManager: AnimeDownloadManager = Injekt.get(), private val sourceManager: AnimeSourceManager = Injekt.get(), + private val localManager: LocalAnimeSourceFileSystem = Injekt.get(), ) : CommonStorageScreenModel( downloadCacheChanges = downloadCache.changes, downloadCacheIsInitializing = downloadCache.isInitializing, libraries = getLibraries.subscribe(), categories = getVisibleCategories.subscribe(), - getDownloadSize = { downloadManager.getDownloadSize(anime) }, - getDownloadCount = { downloadManager.getDownloadCount(anime) }, + getTotalDownloadSize = { downloadManager.getDownloadSize() }, + getDownloadSize = { + if (sourceManager.getOrStub(anime.source).isLocal()) { + localManager.getEpisodesInAnimeDirectory(anime.title) + .map { UniFile.fromFile(it)?.size() ?: 0 } + .sum() + } else { + downloadManager.getDownloadSize(anime) + } + }, + getDownloadCount = { + if (sourceManager.getOrStub(anime.source).isLocal()) { + localManager.getEpisodesInAnimeDirectory(anime.title).count() + } else { + downloadManager.getDownloadCount(anime) + } + }, getId = { id }, getCategoryId = { category }, getTitle = { anime.title }, getThumbnail = { anime.thumbnailUrl }, + isFromLocalSource = { sourceManager.getOrStub(anime.source).isLocal() }, ) { override fun deleteEntry(id: Long) { coroutineScope.launchNonCancellable { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/storage/manga/MangaStorageScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/storage/manga/MangaStorageScreenModel.kt index 4f97095b3e..1070230b6d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/storage/manga/MangaStorageScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/storage/manga/MangaStorageScreenModel.kt @@ -1,14 +1,18 @@ package eu.kanade.tachiyomi.ui.storage.manga import cafe.adriel.voyager.core.model.coroutineScope +import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.data.download.manga.MangaDownloadCache import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager import eu.kanade.tachiyomi.ui.storage.CommonStorageScreenModel +import eu.kanade.tachiyomi.util.size import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.domain.category.manga.interactor.GetVisibleMangaCategories import tachiyomi.domain.entries.manga.interactor.GetLibraryManga import tachiyomi.domain.library.manga.LibraryManga import tachiyomi.domain.source.manga.service.MangaSourceManager +import tachiyomi.source.local.entries.manga.isLocal +import tachiyomi.source.local.io.manga.LocalMangaSourceFileSystem import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -18,17 +22,34 @@ class MangaStorageScreenModel( getVisibleCategories: GetVisibleMangaCategories = Injekt.get(), private val downloadManager: MangaDownloadManager = Injekt.get(), private val sourceManager: MangaSourceManager = Injekt.get(), + private val localManager: LocalMangaSourceFileSystem = Injekt.get(), ) : CommonStorageScreenModel( downloadCacheChanges = downloadCache.changes, downloadCacheIsInitializing = downloadCache.isInitializing, libraries = getLibraries.subscribe(), categories = getVisibleCategories.subscribe(), - getDownloadSize = { downloadManager.getDownloadSize(manga) }, - getDownloadCount = { downloadManager.getDownloadCount(manga) }, + getTotalDownloadSize = { downloadManager.getDownloadSize() }, + getDownloadSize = { + if (sourceManager.getOrStub(manga.source).isLocal()) { + localManager.getChaptersInMangaDirectory(manga.title) + .map { UniFile.fromFile(it)?.size() ?: 0 } + .sum() + } else { + downloadManager.getDownloadSize(manga) + } + }, + getDownloadCount = { + if (sourceManager.getOrStub(manga.source).isLocal()) { + localManager.getChaptersInMangaDirectory(manga.title).count() + } else { + downloadManager.getDownloadCount(manga) + } + }, getId = { id }, getCategoryId = { category }, getTitle = { manga.title }, getThumbnail = { manga.thumbnailUrl }, + isFromLocalSource = { sourceManager.getOrStub(manga.source).isLocal() }, ) { override fun deleteEntry(id: Long) { coroutineScope.launchNonCancellable { diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/anime/LocalAnimeSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/anime/LocalAnimeSource.kt index 15da3f17bc..f3d4d7f86d 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/anime/LocalAnimeSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/anime/LocalAnimeSource.kt @@ -24,7 +24,6 @@ import tachiyomi.domain.items.episode.service.EpisodeRecognition import tachiyomi.source.local.R import tachiyomi.source.local.filter.anime.AnimeOrderBy import tachiyomi.source.local.image.anime.LocalAnimeCoverManager -import tachiyomi.source.local.io.ArchiveAnime import tachiyomi.source.local.io.anime.LocalAnimeSourceFileSystem import uy.kohesive.injekt.injectLazy import java.io.File @@ -155,9 +154,7 @@ actual class LocalAnimeSource( // Episodes override suspend fun getEpisodeList(anime: SAnime): List { - return fileSystem.getFilesInAnimeDirectory(anime.url) - // Only keep supported formats - .filter { it.isDirectory || ArchiveAnime.isSupported(it) } + return fileSystem.getEpisodesInAnimeDirectory(anime.url) .map { episodeFile -> SEpisode.create().apply { url = episodeFile.absolutePath diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt index f762690d32..7696bbe9e7 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt @@ -256,9 +256,7 @@ actual class LocalMangaSource( // Chapters override suspend fun getChapterList(manga: SManga): List { - return fileSystem.getFilesInMangaDirectory(manga.url) - // Only keep supported formats - .filter { it.isDirectory || ArchiveManga.isSupported(it) } + return fileSystem.getChaptersInMangaDirectory(manga.url) .map { chapterFile -> SChapter.create().apply { url = "${manga.url}/${chapterFile.name}" diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt index 23ce1d8fd6..0133177b6a 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt @@ -3,6 +3,7 @@ package tachiyomi.source.local.io.anime import android.content.Context import eu.kanade.tachiyomi.util.storage.DiskUtil import tachiyomi.source.local.R +import tachiyomi.source.local.io.ArchiveAnime import java.io.File actual class LocalAnimeSourceFileSystem( @@ -36,4 +37,10 @@ actual class LocalAnimeSourceFileSystem( // Get all the files inside the filtered folders .flatMap { it.listFiles().orEmpty().toList() } } + + actual fun getEpisodesInAnimeDirectory(name: String): Sequence { + return getFilesInAnimeDirectory(name).filter { + it.isDirectory || ArchiveAnime.isSupported(it) + } + } } diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt index df18d6dc60..1bcde518dd 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt @@ -3,6 +3,7 @@ package tachiyomi.source.local.io.manga import android.content.Context import eu.kanade.tachiyomi.util.storage.DiskUtil import tachiyomi.source.local.R +import tachiyomi.source.local.io.ArchiveManga import java.io.File actual class LocalMangaSourceFileSystem( @@ -36,4 +37,10 @@ actual class LocalMangaSourceFileSystem( // Get all the files inside the filtered folders .flatMap { it.listFiles().orEmpty().toList() } } + + actual fun getChaptersInMangaDirectory(name: String): Sequence { + return getFilesInMangaDirectory(name).filter { + it.isDirectory || ArchiveManga.isSupported(it) + } + } } diff --git a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt index 970dca0ab9..833b0b6f39 100644 --- a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt +++ b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt @@ -11,4 +11,6 @@ expect class LocalAnimeSourceFileSystem { fun getAnimeDirectory(name: String): File? fun getFilesInAnimeDirectory(name: String): Sequence + + fun getEpisodesInAnimeDirectory(name: String): Sequence } diff --git a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt index 7bdc16c2f6..cedb9211e7 100644 --- a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt +++ b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt @@ -11,4 +11,6 @@ expect class LocalMangaSourceFileSystem { fun getMangaDirectory(name: String): File? fun getFilesInMangaDirectory(name: String): Sequence + + fun getChaptersInMangaDirectory(name: String): Sequence }