From 96fecbd5976994aa8cbaee447a11bf2c475ca4ee Mon Sep 17 00:00:00 2001 From: Dark25 Date: Wed, 7 Aug 2024 01:15:29 +0200 Subject: [PATCH] Feat: Show File Size for Downloaded Episodes --- .../presentation/entries/anime/AnimeScreen.kt | 58 +++++++++++++++++++ .../anime/components/AnimeEpisodeListItem.kt | 6 ++ .../components/EpisodeDownloadIndicator.kt | 30 ++++++++++ .../settings/screen/SettingsDataScreen.kt | 32 ++++++++-- .../updates/anime/AnimeUpdatesUiItem.kt | 45 +++++++++++++- .../download/anime/AnimeDownloadProvider.kt | 34 +++++++++++ .../tachiyomi/ui/entries/anime/AnimeScreen.kt | 3 + .../ui/entries/anime/AnimeScreenModel.kt | 11 ++++ .../updates/anime/AnimeUpdatesScreenModel.kt | 6 ++ .../storage/service/StoragePreferences.kt | 3 + .../moko-resources/base/strings-animetail.xml | 1 + 11 files changed, 224 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt index 7c91452dd4..ef9450500e 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt @@ -37,6 +37,7 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -69,6 +70,8 @@ import eu.kanade.presentation.entries.components.MissingItemCountListItem import eu.kanade.presentation.util.formatEpisodeNumber import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.AnimeSource +import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadProvider import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload import eu.kanade.tachiyomi.source.anime.getNameForAnimeInfo import eu.kanade.tachiyomi.ui.browse.anime.extension.details.AnimeSourcePreferencesScreen @@ -76,6 +79,7 @@ import eu.kanade.tachiyomi.ui.entries.anime.AnimeScreenModel import eu.kanade.tachiyomi.ui.entries.anime.EpisodeList import eu.kanade.tachiyomi.util.system.copyToClipboard import kotlinx.coroutines.delay +import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.Episode import tachiyomi.domain.items.episode.service.missingEpisodesCount @@ -90,6 +94,7 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.shouldExpandFAB import tachiyomi.source.local.entries.anime.isLocal +import uy.kohesive.injekt.injectLazy import java.time.Instant import java.util.concurrent.TimeUnit @@ -104,6 +109,9 @@ fun AnimeScreen( episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction, showNextEpisodeAirTime: Boolean, alwaysUseExternalPlayer: Boolean, + // AM (FILE_SIZE) --> + showFileSize: Boolean, + // <-- AM (FILE_SIZE) onBackClicked: () -> Unit, onEpisodeClicked: (episode: Episode, alt: Boolean) -> Unit, onDownloadEpisode: ((List, EpisodeDownloadAction) -> Unit)?, @@ -169,6 +177,9 @@ fun AnimeScreen( episodeSwipeEndAction = episodeSwipeEndAction, showNextEpisodeAirTime = showNextEpisodeAirTime, alwaysUseExternalPlayer = alwaysUseExternalPlayer, + // AM (FILE_SIZE) --> + showFileSize = showFileSize, + // <-- AM (FILE_SIZE) onBackClicked = onBackClicked, onEpisodeClicked = onEpisodeClicked, onDownloadEpisode = onDownloadEpisode, @@ -211,6 +222,9 @@ fun AnimeScreen( episodeSwipeEndAction = episodeSwipeEndAction, showNextEpisodeAirTime = showNextEpisodeAirTime, alwaysUseExternalPlayer = alwaysUseExternalPlayer, + // AM (FILE_SIZE) --> + showFileSize = showFileSize, + // <-- AM (FILE_SIZE) onBackClicked = onBackClicked, onEpisodeClicked = onEpisodeClicked, onDownloadEpisode = onDownloadEpisode, @@ -258,6 +272,9 @@ private fun AnimeScreenSmallImpl( episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction, showNextEpisodeAirTime: Boolean, alwaysUseExternalPlayer: Boolean, + // AM (FILE_SIZE) --> + showFileSize: Boolean, + // <-- AM (FILE_SIZE) onBackClicked: () -> Unit, onEpisodeClicked: (Episode, Boolean) -> Unit, onDownloadEpisode: ((List, EpisodeDownloadAction) -> Unit)?, @@ -532,6 +549,10 @@ private fun AnimeScreenSmallImpl( sharedEpisodeItems( anime = state.anime, + // AM (FILE_SIZE) --> + source = state.source, + showFileSize = showFileSize, + // <-- AM (FILE_SIZE) episodes = listItem, isAnyEpisodeSelected = episodes.fastAny { it.selected }, episodeSwipeStartAction = episodeSwipeStartAction, @@ -558,6 +579,9 @@ fun AnimeScreenLargeImpl( episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction, showNextEpisodeAirTime: Boolean, alwaysUseExternalPlayer: Boolean, + // AM (FILE_SIZE) --> + showFileSize: Boolean, + // <-- AM (FILE_SIZE) onBackClicked: () -> Unit, onEpisodeClicked: (Episode, Boolean) -> Unit, onDownloadEpisode: ((List, EpisodeDownloadAction) -> Unit)?, @@ -821,6 +845,10 @@ fun AnimeScreenLargeImpl( sharedEpisodeItems( anime = state.anime, + // AM (FILE_SIZE) --> + source = state.source, + showFileSize = showFileSize, + // <-- AM (FILE_SIZE) episodes = listItem, isAnyEpisodeSelected = episodes.fastAny { it.selected }, episodeSwipeStartAction = episodeSwipeStartAction, @@ -891,6 +919,10 @@ private fun SharedAnimeBottomActionMenu( private fun LazyListScope.sharedEpisodeItems( anime: Anime, + // AM (FILE_SIZE) --> + source: AnimeSource, + showFileSize: Boolean, + // <-- AM (FILE_SIZE) episodes: List, isAnyEpisodeSelected: Boolean, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, @@ -917,6 +949,26 @@ private fun LazyListScope.sharedEpisodeItems( MissingItemCountListItem(count = episodeItem.count) } is EpisodeList.Item -> { + // AM (FILE_SIZE) --> + var fileSizeAsync: Long? by remember { mutableStateOf(episodeItem.fileSize) } + val isEpisodeDownloaded = episodeItem.downloadState == AnimeDownload.State.DOWNLOADED + if (isEpisodeDownloaded && showFileSize && fileSizeAsync == null) { + LaunchedEffect(episodeItem, Unit) { + fileSizeAsync = withIOContext { + animeDownloadProvider.getEpisodeFileSize( + episodeItem.episode.name, + episodeItem.episode.url, + episodeItem.episode.scanlator, + // AM (CUSTOM_INFORMATION) --> + anime.ogTitle, + // <-- AM (CUSTOM_INFORMATION) + source, + ) + } + episodeItem.fileSize = fileSizeAsync + } + } + // <-- AM (FILE_SIZE) AnimeEpisodeListItem( title = if (anime.displayMode == Anime.EPISODE_DISPLAY_NUMBER) { stringResource( @@ -965,6 +1017,9 @@ private fun LazyListScope.sharedEpisodeItems( onEpisodeSwipe = { onEpisodeSwipe(episodeItem, it) }, + // AM (FILE_SIZE) --> + fileSize = fileSizeAsync, + // <-- AM (FILE_SIZE) ) } } @@ -1014,3 +1069,6 @@ private fun formatTime(milliseconds: Long, useDayFormat: Boolean = false): Strin ) } } +// AM (FILE_SIZE) --> +private val animeDownloadProvider: AnimeDownloadProvider by injectLazy() +// <-- AM (FILE_SIZE) diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt index 14393baa31..5acd992057 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt @@ -67,6 +67,9 @@ fun AnimeEpisodeListItem( onClick: () -> Unit, onDownloadClick: ((EpisodeDownloadAction) -> Unit)?, onEpisodeSwipe: (LibraryPreferences.EpisodeSwipeAction) -> Unit, + // AM (FILE_SIZE) --> + fileSize: Long?, + // <-- AM (FILE_SIZE) modifier: Modifier = Modifier, ) { val start = getSwipeAction( @@ -181,6 +184,9 @@ fun AnimeEpisodeListItem( downloadStateProvider = downloadStateProvider, downloadProgressProvider = downloadProgressProvider, onClick = { onDownloadClick?.invoke(it) }, + // AM (FILE_SIZE) --> + fileSize = fileSize, + // <-- AM (FILE_SIZE) ) } } diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/components/EpisodeDownloadIndicator.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/components/EpisodeDownloadIndicator.kt index 47558a1bd8..895041f56c 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/components/EpisodeDownloadIndicator.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/components/EpisodeDownloadIndicator.kt @@ -2,6 +2,7 @@ package eu.kanade.presentation.entries.anime.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle @@ -25,6 +26,7 @@ import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import eu.kanade.presentation.components.ArrowModifier import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.IndicatorModifier @@ -52,6 +54,9 @@ fun EpisodeDownloadIndicator( downloadStateProvider: () -> AnimeDownload.State, downloadProgressProvider: () -> Int, onClick: (EpisodeDownloadAction) -> Unit, + // AM (FILE_SIZE) --> + fileSize: Long?, + // <-- AM (FILE_SIZE) modifier: Modifier = Modifier, ) { when (val downloadState = downloadStateProvider()) { @@ -66,10 +71,16 @@ fun EpisodeDownloadIndicator( downloadState = downloadState, downloadProgressProvider = downloadProgressProvider, onClick = onClick, + // AM (FILE_SIZE) --> + fileSize = fileSize, + // <-- AM (FILE_SIZE) ) AnimeDownload.State.DOWNLOADED -> DownloadedIndicator( enabled = enabled, modifier = modifier, + // AM (FILE_SIZE) --> + fileSize = fileSize, + // <-- AM (FILE_SIZE) onClick = onClick, ) AnimeDownload.State.ERROR -> ErrorIndicator( @@ -113,6 +124,9 @@ private fun DownloadingIndicator( downloadState: AnimeDownload.State, downloadProgressProvider: () -> Int, onClick: (EpisodeDownloadAction) -> Unit, + // AM (FILE_SIZE) --> + fileSize: Long?, + // <-- AM (FILE_SIZE) modifier: Modifier = Modifier, ) { var isMenuExpanded by remember { mutableStateOf(false) } @@ -192,8 +206,24 @@ private fun DownloadedIndicator( enabled: Boolean, modifier: Modifier = Modifier, onClick: (EpisodeDownloadAction) -> Unit, + // AM (FILE_SIZE) --> + fileSize: Long?, + // <-- AM (FILE_SIZE) ) { var isMenuExpanded by remember { mutableStateOf(false) } + + // AM (FILE_SIZE) --> + if (fileSize != null) { + Text( + text = "${fileSize / 1024 / 1024}MB", + maxLines = 1, + style = MaterialTheme.typography.bodyMedium + .copy(color = MaterialTheme.colorScheme.primary, fontSize = 12.sp), + modifier = Modifier.padding(all = 10.dp), + ) + } + // <-- AM (FILE_SIZE) + Box( modifier = modifier .size(IconButtonTokens.StateLayerSize) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index bda74d445c..e85eecd555 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -52,6 +53,7 @@ import eu.kanade.presentation.util.relativeTimeSpanString import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob import eu.kanade.tachiyomi.data.cache.ChapterCache +import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadCache import eu.kanade.tachiyomi.data.sync.SyncDataJob import eu.kanade.tachiyomi.data.sync.SyncManager import eu.kanade.tachiyomi.data.sync.service.GoogleDriveService @@ -61,6 +63,8 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.toast import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.launch import logcat.LogPriority import tachiyomi.core.common.i18n.stringResource @@ -77,6 +81,7 @@ import tachiyomi.presentation.core.util.collectAsState import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get + @Suppress("TooManyFunctions") object SettingsDataScreen : SearchableSettings { @@ -109,9 +114,10 @@ object SettingsDataScreen : SearchableSettings { return persistentListOf( getStorageLocationPref(storagePreferences = storagePreferences), Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.pref_storage_location_info)), - getBackupAndRestoreGroup(backupPreferences = backupPreferences), - getDataGroup(), + // AM (FILE_SIZE) --> + getDataGroup(storagePreferences = storagePreferences), + // <-- AM (FILE_SIZE) ) + getSyncPreferences(syncPreferences = syncPreferences, syncService = syncService) } @@ -188,7 +194,6 @@ object SettingsDataScreen : SearchableSettings { private fun getBackupAndRestoreGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup { val context = LocalContext.current val navigator = LocalNavigator.currentOrThrow - val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState() val chooseBackup = rememberLauncherForActivityResult( @@ -280,7 +285,7 @@ object SettingsDataScreen : SearchableSettings { } @Composable - private fun getDataGroup(): Preference.PreferenceGroup { + private fun getDataGroup(storagePreferences: StoragePreferences): Preference.PreferenceGroup { val context = LocalContext.current val navigator = LocalNavigator.currentOrThrow val scope = rememberCoroutineScope() @@ -290,6 +295,18 @@ object SettingsDataScreen : SearchableSettings { var cacheReadableSizeSema by remember { mutableIntStateOf(0) } val cacheReadableSize = remember(cacheReadableSizeSema) { chapterCache.readableSize } + // AM (FILE_SIZE) --> + LaunchedEffect(Unit) { + storagePreferences.showEpisodeFileSize().changes() + .drop(1) + .collectLatest { value -> + if (value) { + Injekt.get().invalidateCache() + } + } + } + // <-- AM (FILE_SIZE) + return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_storage_usage), preferenceItems = persistentListOf( @@ -306,6 +323,13 @@ object SettingsDataScreen : SearchableSettings { ) }, + // AM (FILE_SIZE) --> + Preference.PreferenceItem.SwitchPreference( + pref = storagePreferences.showEpisodeFileSize(), + title = stringResource(MR.strings.pref_show_downloaded_episode_file_size), + ), + // <-- AM (FILE_SIZE) + Preference.PreferenceItem.TextPreference( title = stringResource(MR.strings.label_storage), icon = Icons.Outlined.Storage, diff --git a/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt b/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt index d42157e139..5173eb4e94 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt @@ -1,5 +1,4 @@ package eu.kanade.presentation.updates.anime - import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -20,8 +19,10 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -38,8 +39,12 @@ import eu.kanade.presentation.entries.anime.components.EpisodeDownloadIndicator import eu.kanade.presentation.entries.components.DotSeparatorText import eu.kanade.presentation.entries.components.ItemCover import eu.kanade.presentation.util.relativeTimeSpanString +import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadProvider import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesItem +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.source.anime.service.AnimeSourceManager +import tachiyomi.domain.storage.service.StoragePreferences import tachiyomi.domain.updates.anime.model.AnimeUpdatesWithRelations import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.ListGroupHeader @@ -47,6 +52,7 @@ import tachiyomi.presentation.core.components.material.ReadItemAlpha import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.selectedBackground +import uy.kohesive.injekt.injectLazy import java.util.concurrent.TimeUnit internal fun LazyListScope.animeUpdatesLastUpdatedItem( @@ -134,6 +140,9 @@ internal fun LazyListScope.animeUpdatesUiItems( }.takeIf { !selectionMode }, downloadStateProvider = updatesItem.downloadStateProvider, downloadProgressProvider = updatesItem.downloadProgressProvider, + // AM (FILE_SIZE) --> + updatesItem = updatesItem, + // <-- AM (FILE_SIZE) ) } } @@ -152,6 +161,9 @@ private fun AnimeUpdatesUiItem( // Download Indicator downloadStateProvider: () -> AnimeDownload.State, downloadProgressProvider: () -> Int, + // AM (FILE_SIZE) --> + updatesItem: AnimeUpdatesItem, + // <-- AM (FILE_SIZE) modifier: Modifier = Modifier, ) { val haptic = LocalHapticFeedback.current @@ -236,6 +248,29 @@ private fun AnimeUpdatesUiItem( } } } +// AM (FILE_SIZE) --> + var fileSizeAsync: Long? by remember { mutableStateOf(updatesItem.fileSize) } + if (downloadStateProvider() == AnimeDownload.State.DOWNLOADED && + storagePreferences.showEpisodeFileSize().get() && + fileSizeAsync == null + ) { + LaunchedEffect(update, Unit) { + fileSizeAsync = withIOContext { + animeDownloadProvider.getEpisodeFileSize( + update.episodeName, + null, + update.scanlator, + // AM (CUSTOM_INFORMATION) --> + update.animeTitle, + // <-- AM (CUSTOM_INFORMATION) + animeSourceManager.getOrStub(update.sourceId), + ) + } + updatesItem.fileSize = fileSizeAsync + } + } + // <-- AM (FILE_SIZE) + EpisodeDownloadIndicator( enabled = onDownloadEpisode != null, @@ -243,6 +278,9 @@ private fun AnimeUpdatesUiItem( downloadStateProvider = downloadStateProvider, downloadProgressProvider = downloadProgressProvider, onClick = { onDownloadEpisode?.invoke(it) }, + // AM (FILE_SIZE) --> + fileSize = fileSizeAsync, + // <-- AM (FILE_SIZE) ) } } @@ -266,3 +304,8 @@ private fun formatProgress(milliseconds: Long): String { ) } } +// AM (FILE_SIZE) --> +private val storagePreferences: StoragePreferences by injectLazy() +private val animeDownloadProvider: AnimeDownloadProvider by injectLazy() +private val animeSourceManager: AnimeSourceManager by injectLazy() +// <-- AM (FILE_SIZE) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt index 215692097f..308fd3fd74 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.download.anime import android.content.Context import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.animesource.AnimeSource +import eu.kanade.tachiyomi.util.size import eu.kanade.tachiyomi.util.storage.DiskUtil import logcat.LogPriority import tachiyomi.core.common.i18n.stringResource @@ -14,6 +15,9 @@ import tachiyomi.domain.storage.service.StorageManager import tachiyomi.i18n.MR import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import tachiyomi.source.local.entries.anime.isLocal +import tachiyomi.source.local.io.anime.LocalAnimeSourceFileSystem + /** * This class is used to provide the directories where the downloads should be saved. @@ -24,6 +28,9 @@ import uy.kohesive.injekt.api.get class AnimeDownloadProvider( private val context: Context, private val storageManager: StorageManager = Injekt.get(), + // AM (FILE_SIZE) --> + private val localFileSystem: LocalAnimeSourceFileSystem = Injekt.get(), + // <-- AM (FILE_SIZE) ) { private val downloadsDir: UniFile? @@ -183,4 +190,31 @@ class AnimeDownloadProvider( val oldEpisodeDirName = getOldEpisodeDirName(episodeName, episodeScanlator) return listOf(episodeDirName, oldEpisodeDirName) } + + // AM (FILE_SIZE) --> + /** + * Returns an episode file size in bytes. + * Returns null if the episode is not found in expected location + * + * @param episodeName the name of the episode to query. + * @param episodeScanlator scanlator of the episode to query + * @param animeTitle the title of the anime + * @param animeSource the source of the anime + */ + fun getEpisodeFileSize( + episodeName: String, + episodeUrl: String?, + episodeScanlator: String?, + animeTitle: String, + animeSource: AnimeSource?, + ): Long? { + if (animeSource == null) return null + return if (animeSource.isLocal()) { + val (animeDirName, episodeDirName) = episodeUrl?.split('/', limit = 2) ?: return null + localFileSystem.getBaseDirectory()?.findFile(animeDirName )?.findFile(episodeDirName)?.size() + } else { + findEpisodeDir(episodeName, episodeScanlator, animeTitle, animeSource)?.size() + } + } + // <-- AM (FILE_SIZE) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt index 7a7a4bfedf..4083d7d77f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt @@ -125,6 +125,9 @@ class AnimeScreen( episodeSwipeEndAction = screenModel.episodeSwipeEndAction, showNextEpisodeAirTime = screenModel.showNextEpisodeAirTime, alwaysUseExternalPlayer = screenModel.alwaysUseExternalPlayer, + // AM (FILE_SIZE) --> + showFileSize = screenModel.showFileSize, + // <-- AM (FILE_SIZE) onBackClicked = navigator::pop, onEpisodeClicked = { episode, alt -> scope.launchIO { 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 4a57bd7da2..7fabf4e2ee 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 @@ -82,6 +82,7 @@ import tachiyomi.domain.items.episode.service.calculateEpisodeGap import tachiyomi.domain.items.episode.service.getEpisodeSort import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.source.anime.service.AnimeSourceManager +import tachiyomi.domain.storage.service.StoragePreferences import tachiyomi.domain.track.anime.interactor.GetAnimeTracks import tachiyomi.i18n.MR import tachiyomi.source.local.entries.anime.LocalAnimeSource @@ -121,6 +122,9 @@ class AnimeScreenModel( private val animeRepository: AnimeRepository = Injekt.get(), internal val setAnimeViewerFlags: SetAnimeViewerFlags = Injekt.get(), val snackbarHostState: SnackbarHostState = SnackbarHostState(), + // AM (FILE_SIZE) --> + private val storagePreferences: StoragePreferences = Injekt.get(), + // <-- AM (FILE_SIZE) ) : StateScreenModel(State.Loading) { private val successState: State.Success? @@ -156,6 +160,10 @@ class AnimeScreenModel( internal val autoOpenTrack: Boolean get() = successState?.trackingAvailable == true && trackPreferences.trackOnAddingToLibrary().get() + // AM (FILE_SIZE) --> + val showFileSize = storagePreferences.showEpisodeFileSize().get() + // <-- AM (FILE_SIZE) + /** * Helper function to update the UI state only if it's currently in success state */ @@ -1281,6 +1289,9 @@ sealed class EpisodeList { val episode: Episode, val downloadState: AnimeDownload.State, val downloadProgress: Int, + // AM (FILE_SIZE) --> + var fileSize: Long? = null, + // <-- AM (FILE_SIZE) val selected: Boolean = false, ) : EpisodeList() { val id = episode.id diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt index daec3dac04..fd32e34f90 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt @@ -124,6 +124,9 @@ class AnimeUpdatesScreenModel( downloadStateProvider = { downloadState }, downloadProgressProvider = { activeDownload?.progress ?: 0 }, selected = update.episodeId in selectedEpisodeIds, + // AM (FILE_SIZE) --> + fileSize = null, + // <-- AM (FILE_SIZE) ) } .toPersistentList() @@ -425,4 +428,7 @@ data class AnimeUpdatesItem( val downloadStateProvider: () -> AnimeDownload.State, val downloadProgressProvider: () -> Int, val selected: Boolean = false, + // AM (FILE_SIZE) --> + var fileSize: Long?, + // <-- AM (FILE_SIZE) ) diff --git a/domain/src/main/java/tachiyomi/domain/storage/service/StoragePreferences.kt b/domain/src/main/java/tachiyomi/domain/storage/service/StoragePreferences.kt index f29949cff0..a6752ae384 100644 --- a/domain/src/main/java/tachiyomi/domain/storage/service/StoragePreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/storage/service/StoragePreferences.kt @@ -10,4 +10,7 @@ class StoragePreferences( ) { fun baseStorageDirectory() = preferenceStore.getString(Preference.appStateKey("storage_dir"), folderProvider.path()) + // AM (FILE_SIZE) --> + fun showEpisodeFileSize() = preferenceStore.getBoolean("pref_show_downloaded_episode_size", true) + // <-- AM (FILE_SIZE) } diff --git a/i18n/src/commonMain/moko-resources/base/strings-animetail.xml b/i18n/src/commonMain/moko-resources/base/strings-animetail.xml index 2f6f79993d..3293a1c06e 100644 --- a/i18n/src/commonMain/moko-resources/base/strings-animetail.xml +++ b/i18n/src/commonMain/moko-resources/base/strings-animetail.xml @@ -30,6 +30,7 @@ Cast Settings Enable or disable Cast + Show downloaded episode file size Fork Settings