Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filesize #1875

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -69,13 +70,17 @@ 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.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
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.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.entries.anime.model.Anime
import tachiyomi.domain.items.episode.model.Episode
import tachiyomi.domain.items.episode.service.missingEpisodesCount
Expand All @@ -90,9 +95,13 @@ 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

private val preferences: DownloadPreferences by injectLazy()
private val animeDownloadProvider: AnimeDownloadProvider by injectLazy()

@Composable
fun AnimeScreen(
state: AnimeScreenModel.State.Success,
Expand Down Expand Up @@ -512,6 +521,7 @@ private fun AnimeScreenSmallImpl(

sharedEpisodeItems(
anime = state.anime,
state = state,
episodes = listItem,
isAnyEpisodeSelected = episodes.fastAny { it.selected },
episodeSwipeStartAction = episodeSwipeStartAction,
Expand Down Expand Up @@ -791,6 +801,7 @@ fun AnimeScreenLargeImpl(

sharedEpisodeItems(
anime = state.anime,
state = state,
episodes = listItem,
isAnyEpisodeSelected = episodes.fastAny { it.selected },
episodeSwipeStartAction = episodeSwipeStartAction,
Expand Down Expand Up @@ -861,6 +872,7 @@ private fun SharedAnimeBottomActionMenu(

private fun LazyListScope.sharedEpisodeItems(
anime: Anime,
state: AnimeScreenModel.State.Success,
episodes: List<EpisodeList>,
isAnyEpisodeSelected: Boolean,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
Expand All @@ -887,6 +899,24 @@ private fun LazyListScope.sharedEpisodeItems(
MissingItemCountListItem(count = episodeItem.count)
}
is EpisodeList.Item -> {
var fileSizeAsync: Long? by remember { mutableStateOf(episodeItem.fileSize) }
if (episodeItem.downloadState == AnimeDownload.State.DOWNLOADED &&
preferences.showEpisodeFileSize().get() && fileSizeAsync == null
) {
LaunchedEffect(episodeItem, Unit) {
fileSizeAsync = withContext(Dispatchers.IO) {
animeDownloadProvider.getEpisodeFileSize(
episodeItem.episode.name,
episodeItem.episode.url,
episodeItem.episode.scanlator,
state.anime.title,
state.source,
)
}
episodeItem.fileSize = fileSizeAsync
}
}

AnimeEpisodeListItem(
title = if (anime.displayMode == Anime.EPISODE_DISPLAY_NUMBER) {
stringResource(
Expand Down Expand Up @@ -935,6 +965,7 @@ private fun LazyListScope.sharedEpisodeItems(
onEpisodeSwipe = {
onEpisodeSwipe(episodeItem, it)
},
fileSize = fileSizeAsync,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ fun AnimeEpisodeListItem(
bookmark: Boolean,
selected: Boolean,
downloadIndicatorEnabled: Boolean,
fileSize: Long?,
downloadStateProvider: () -> AnimeDownload.State,
downloadProgressProvider: () -> Int,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
Expand Down Expand Up @@ -101,6 +102,7 @@ fun AnimeEpisodeListItem(
onLongClick = onLongClick,
)
.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.weight(1f),
Expand Down Expand Up @@ -181,6 +183,7 @@ fun AnimeEpisodeListItem(
downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider,
onClick = { onDownloadClick?.invoke(it) },
fileSize = fileSize,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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
Expand All @@ -37,6 +38,8 @@ import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.IconButtonTokens
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.secondaryItemAlpha
import java.math.BigDecimal
import java.math.RoundingMode

enum class EpisodeDownloadAction {
START,
Expand All @@ -49,6 +52,7 @@ enum class EpisodeDownloadAction {
@Composable
fun EpisodeDownloadIndicator(
enabled: Boolean,
fileSize: Long?,
downloadStateProvider: () -> AnimeDownload.State,
downloadProgressProvider: () -> Int,
onClick: (EpisodeDownloadAction) -> Unit,
Expand All @@ -60,18 +64,22 @@ fun EpisodeDownloadIndicator(
modifier = modifier,
onClick = onClick,
)

AnimeDownload.State.QUEUE, AnimeDownload.State.DOWNLOADING -> DownloadingIndicator(
enabled = enabled,
modifier = modifier,
downloadState = downloadState,
downloadProgressProvider = downloadProgressProvider,
onClick = onClick,
)

AnimeDownload.State.DOWNLOADED -> DownloadedIndicator(
enabled = enabled,
modifier = modifier,
onClick = onClick,
fileSize,
)

AnimeDownload.State.ERROR -> ErrorIndicator(
enabled = enabled,
modifier = modifier,
Expand Down Expand Up @@ -192,8 +200,21 @@ private fun DownloadedIndicator(
enabled: Boolean,
modifier: Modifier = Modifier,
onClick: (EpisodeDownloadAction) -> Unit,
fileSize: Long?,
) {
var isMenuExpanded by remember { mutableStateOf(false) }

if (fileSize != null) {
Text(
text = formatFileSize(fileSize),
maxLines = 1,
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.primary,
fontSize = 12.sp,
),
)
}

Box(
modifier = modifier
.size(IconButtonTokens.StateLayerSize)
Expand Down Expand Up @@ -223,6 +244,16 @@ private fun DownloadedIndicator(
}
}

private fun formatFileSize(fileSize: Long): String {
val megaByteSize = fileSize / 1000.0 / 1000.0
return if (megaByteSize > 900) {
val gigaByteSize = megaByteSize / 1000.0
"${BigDecimal(gigaByteSize).setScale(2, RoundingMode.HALF_EVEN)} GB"
} else {
"${BigDecimal(megaByteSize).setScale(0, RoundingMode.HALF_EVEN)} MB"
}
}

@Composable
private fun ErrorIndicator(
enabled: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ object SettingsDownloadScreen : SearchableSettings {
pref = downloadPreferences.downloadOnlyOverWifi(),
title = stringResource(MR.strings.connected_to_wifi),
),
Preference.PreferenceItem.SwitchPreference(
pref = downloadPreferences.showEpisodeFileSize(),
title = stringResource(MR.strings.show_downloaded_episode_size),
subtitle = stringResource(MR.strings.safe_download_summary),
),
Preference.PreferenceItem.SwitchPreference(
pref = downloadPreferences.safeDownload(),
title = stringResource(MR.strings.safe_download),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,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
Expand All @@ -39,17 +41,27 @@ import eu.kanade.presentation.entries.components.DotSeparatorText
import eu.kanade.presentation.entries.components.ItemCover
import eu.kanade.presentation.util.animateItemFastScroll
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.source.anime.service.AnimeSourceManager
import tachiyomi.domain.updates.anime.model.AnimeUpdatesWithRelations
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ListGroupHeader
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
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

private val preferences: DownloadPreferences by injectLazy()
private val animeDownloadProvider: AnimeDownloadProvider by injectLazy()
private val animeSourceManager: AnimeSourceManager by injectLazy()

internal fun LazyListScope.animeUpdatesLastUpdatedItem(
lastUpdated: Long,
) {
Expand Down Expand Up @@ -135,6 +147,7 @@ internal fun LazyListScope.animeUpdatesUiItems(
}.takeIf { !selectionMode },
downloadStateProvider = updatesItem.downloadStateProvider,
downloadProgressProvider = updatesItem.downloadProgressProvider,
updatesItem = updatesItem,
)
}
}
Expand All @@ -153,6 +166,7 @@ private fun AnimeUpdatesUiItem(
// Download Indicator
downloadStateProvider: () -> AnimeDownload.State,
downloadProgressProvider: () -> Int,
updatesItem: AnimeUpdatesItem,
modifier: Modifier = Modifier,
) {
val haptic = LocalHapticFeedback.current
Expand Down Expand Up @@ -238,12 +252,32 @@ private fun AnimeUpdatesUiItem(
}
}

var fileSizeAsync: Long? by remember { mutableStateOf(updatesItem.fileSize) }
if (downloadStateProvider() == AnimeDownload.State.DOWNLOADED &&
preferences.showEpisodeFileSize().get() &&
fileSizeAsync == null
) {
LaunchedEffect(update, Unit) {
fileSizeAsync = withContext(Dispatchers.IO) {
animeDownloadProvider.getEpisodeFileSize(
update.episodeName,
null,
update.scanlator,
update.animeTitle,
animeSourceManager.getOrStub(update.sourceId),
)
}
updatesItem.fileSize = fileSizeAsync
}
}

EpisodeDownloadIndicator(
enabled = onDownloadEpisode != null,
modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider,
onClick = { onDownloadEpisode?.invoke(it) },
fileSize = fileSizeAsync,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -12,6 +13,8 @@ import tachiyomi.domain.entries.anime.model.Anime
import tachiyomi.domain.items.episode.model.Episode
import tachiyomi.domain.storage.service.StorageManager
import tachiyomi.i18n.MR
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

Expand All @@ -24,6 +27,7 @@ import uy.kohesive.injekt.api.get
class AnimeDownloadProvider(
private val context: Context,
private val storageManager: StorageManager = Injekt.get(),
private val localFileSystem: LocalAnimeSourceFileSystem = Injekt.get(),
) {

private val downloadsDir: UniFile?
Expand Down Expand Up @@ -183,4 +187,29 @@ class AnimeDownloadProvider(
val oldEpisodeDirName = getOldEpisodeDirName(episodeName, episodeScanlator)
return listOf(episodeDirName, oldEpisodeDirName)
}

/**
* 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()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,7 @@ sealed class EpisodeList {
val episode: Episode,
val downloadState: AnimeDownload.State,
val downloadProgress: Int,
var fileSize: Long? = null,
val selected: Boolean = false,
) : EpisodeList() {
val id = episode.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,4 +425,5 @@ data class AnimeUpdatesItem(
val downloadStateProvider: () -> AnimeDownload.State,
val downloadProgressProvider: () -> Int,
val selected: Boolean = false,
var fileSize: Long? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class DownloadPreferences(
private val preferenceStore: PreferenceStore,
) {

fun showEpisodeFileSize() = preferenceStore.getBoolean("pref_downloaded_episode_size", true)

fun downloadOnlyOverWifi() = preferenceStore.getBoolean(
"pref_download_only_over_wifi_key",
true,
Expand Down
1 change: 1 addition & 0 deletions i18n/src/commonMain/moko-resources/base/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1317,4 +1317,5 @@
<string name="download_threads_number_summary">Number of threads to use for downloading, might get your IP blocked if too high, usually 4 is a good number to avoid heavy load on source servers.</string>
<string name="download_speed_limit">Download speed limit</string>
<string name="download_speed_limit_hint">Set to 0 to disable the speed limit.</string>
<string name="show_downloaded_episode_size">Show downloaded episode size</string>
</resources>
Loading
Loading