From 84f13b1c8645655d9d3cefb6c5db7e3e484ab06d Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Tue, 26 Aug 2025 13:49:22 +0200 Subject: [PATCH] add no free space warning --- .../database/viewmodel/FormatViewModel.kt | 22 ++++++++++++ .../ui/downloadcard/DownloadAudioFragment.kt | 27 ++++++++++++--- .../DownloadMultipleBottomSheetDialog.kt | 16 ++++++++- .../ui/downloadcard/DownloadVideoFragment.kt | 34 ++++++++++++++----- .../java/com/deniscerri/ytdl/util/UiUtil.kt | 9 ++++- app/src/main/res/values/strings.xml | 1 + 6 files changed, 94 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/FormatViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/FormatViewModel.kt index 6e4e8e7b..7def02c2 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/FormatViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/FormatViewModel.kt @@ -22,6 +22,7 @@ import com.deniscerri.ytdl.ui.downloadcard.FormatSelectionBottomSheetDialog.Form import com.deniscerri.ytdl.ui.downloadcard.FormatTuple import com.deniscerri.ytdl.ui.downloadcard.MultipleItemFormatTuple import com.deniscerri.ytdl.util.Extensions.isYoutubeURL +import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FormatUtil import com.deniscerri.ytdl.util.UiUtil import com.google.android.material.snackbar.Snackbar @@ -30,11 +31,14 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.File import java.text.Normalizer.Form +import kotlin.text.compareTo class FormatViewModel(private val application: Application) : AndroidViewModel(application) { private val downloadRepository: DownloadRepository @@ -56,6 +60,9 @@ class FormatViewModel(private val application: Application) : AndroidViewModel(a var sortBy = FormatSorting.valueOf(sharedPreferences.getString("format_order", "filesize")!!) var filterBy = MutableStateFlow(FormatCategory.valueOf(sharedPreferences.getString("format_filter", "ALL")!!)) + private val _noFreeSpace = MutableSharedFlow() + val noFreeSpace = _noFreeSpace.asSharedFlow() + init { downloadRepository = DownloadRepository(DBManager.getInstance(application).downloadDao) formats = combine(listOf(selectedItemsSharedFlow, filterBy)) { f -> @@ -245,4 +252,19 @@ class FormatViewModel(private val application: Application) : AndroidViewModel(a return formatsToReturn } + + fun checkFreeSpace(size: Long, path: String) = viewModelScope.launch { + _noFreeSpace.emit(null) + if (size > 10L) { + File(FileUtil.formatPath(path)).apply { + if (size > this.freeSpace) { + val warningTxt = application.getString(R.string.no_free_space_warning) + + "\n" + "${application.getString(R.string.file_size)}:\t${FileUtil.convertFileSize(size)}" + + "\n" + "${application.getString(R.string.freespace)}:\t${FileUtil.convertFileSize(this.freeSpace)}" + + _noFreeSpace.emit(warningTxt) + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt index 028f7efd..fd8a280b 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt @@ -45,6 +45,8 @@ import com.google.gson.Gson import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File @@ -227,12 +229,14 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private val formatCard = view.findViewById(R.id.format_card_constraintLayout) val chosenFormat = downloadItem.format - UiUtil.populateFormatCard(requireContext(), formatCard, chosenFormat, null, showSize = downloadItem.downloadSections.isEmpty()) + val filesize = UiUtil.populateFormatCard(requireContext(), formatCard, chosenFormat, null, showSize = downloadItem.downloadSections.isEmpty()) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) val listener = object : OnFormatClickListener { override fun onFormatClick(formatTuple: FormatTuple) { formatTuple.format?.apply { downloadItem.format = this - UiUtil.populateFormatCard(requireContext(), formatCard, this, null, showSize = downloadItem.downloadSections.isEmpty()) + val filesize = UiUtil.populateFormatCard(requireContext(), formatCard, this, null, showSize = downloadItem.downloadSections.isEmpty()) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) } } @@ -253,7 +257,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private val preferredFormat = downloadViewModel.getFormat(formats, Type.audio) downloadItem.format = preferredFormat downloadItem.allFormats = formats - UiUtil.populateFormatCard(requireContext(), formatCard, preferredFormat, null, showSize = downloadItem.downloadSections.isEmpty()) + val filesize = UiUtil.populateFormatCard(requireContext(), formatCard, preferredFormat, null, showSize = downloadItem.downloadSections.isEmpty()) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) } } formatCard.setOnClickListener{ @@ -359,7 +364,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private }, cutValueChanged = { downloadItem.downloadSections = it - UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, showSize = downloadItem.downloadSections.isEmpty()) + val filesize = UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, showSize = downloadItem.downloadSections.isEmpty()) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) if (it.isNotBlank()){ downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.applyFilenameTemplateForCuts() @@ -392,6 +398,16 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private } } } + + lifecycleScope.launch { + formatViewModel.noFreeSpace.collectLatest { + if (it != null) { + val snack = Snackbar.make(view, it, Snackbar.LENGTH_INDEFINITE) + snack.setTextMaxLines(10) + snack.show() + } + } + } } @SuppressLint("RestrictedApi") @@ -400,7 +416,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private formats.find { it.format_id == formatID }?.apply { downloadItem.format = this val formatCard = requireView().findViewById(R.id.format_card_constraintLayout) - UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, listOf(), showSize = downloadItem.downloadSections.isEmpty()) + val filesize = UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, listOf(), showSize = downloadItem.downloadSections.isEmpty()) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt index f89f1c0a..84c82bda 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt @@ -11,6 +11,7 @@ import android.graphics.Canvas import android.graphics.Color import android.os.Build import android.os.Bundle +import android.os.Environment import android.util.DisplayMetrics import android.view.LayoutInflater import android.view.MenuItem @@ -71,6 +72,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.math.abs @@ -196,6 +198,16 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure } } + lifecycleScope.launch { + formatViewModel.noFreeSpace.collectLatest { + if (it != null) { + val snack = Snackbar.make(view, it, Snackbar.LENGTH_INDEFINITE) + snack.setTextMaxLines(10) + snack.show() + } + } + } + lifecycleScope.launch { downloadViewModel.processingDownloads.collectLatest { items -> processingItemsCount = items.size @@ -1013,10 +1025,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure } if (fileSizes.all { it > 5L }){ - val size = FileUtil.convertFileSize(fileSizes.sum()) + val filesizeRaw = fileSizes.sum() + val size = FileUtil.convertFileSize(filesizeRaw) itemsFileSize = fileSizes.sum() filesize.isVisible = size != "?" && !listAdapter.isCheckingItems() filesize.text = "${getString(R.string.file_size)}: >~ $size" + formatViewModel.checkFreeSpace(filesizeRaw, Environment.getExternalStorageDirectory().path) }else{ filesize.visibility = View.GONE } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt index 199b35f2..6d7a3c35 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt @@ -46,6 +46,8 @@ import com.google.gson.Gson import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File @@ -244,14 +246,15 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private val formatCard = view.findViewById(R.id.format_card_constraintLayout) val chosenFormat = downloadItem.format - - UiUtil.populateFormatCard( + val chosenAudioFormats = downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) } + val filesize = UiUtil.populateFormatCard( requireContext(), formatCard, chosenFormat, - downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) }, + chosenAudioFormats, showSize = downloadItem.downloadSections.isEmpty() ) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) val listener = object : OnFormatClickListener { override fun onFormatClick(formatTuple: FormatTuple) { @@ -262,10 +265,11 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private formatTuple.audioFormats?.map { it.format_id }?.let { downloadItem.videoPreferences.audioFormatIDs.addAll(it) } - UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, + val filesize = UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, if(downloadItem.videoPreferences.removeAudio) listOf() else formatTuple.audioFormats, showSize = downloadItem.downloadSections.isEmpty() ) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) } override fun onFormatsUpdated(allFormats: List) { @@ -287,10 +291,11 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private val preferredAudioFormats = downloadViewModel.getPreferredAudioFormats(formats) downloadItem.format = preferredFormat downloadItem.allFormats = formats - UiUtil.populateFormatCard(requireContext(), formatCard, preferredFormat, + val filesize = UiUtil.populateFormatCard(requireContext(), formatCard, preferredFormat, if(downloadItem.videoPreferences.removeAudio) listOf() else formats.filter { preferredAudioFormats.contains(it.format_id) }, showSize = downloadItem.downloadSections.isEmpty() ) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) } } @@ -399,7 +404,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private }, cutValueChanged = { downloadItem.downloadSections = it - UiUtil.populateFormatCard( + val filesize = UiUtil.populateFormatCard( requireContext(), formatCard, downloadItem.format, @@ -407,6 +412,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private else downloadItem.allFormats.filter { f -> downloadItem.videoPreferences.audioFormatIDs.contains(f.format_id) }, showSize = downloadItem.downloadSections.isEmpty() ) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) if (it.isNotBlank()){ downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.applyFilenameTemplateForCuts() @@ -428,13 +434,14 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private }, removeAudioClicked = { downloadItem.videoPreferences.removeAudio = it - UiUtil.populateFormatCard( + val filesize = UiUtil.populateFormatCard( requireContext(), formatCard, downloadItem.format, if (it) listOf() else downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) }, showSize = downloadItem.downloadSections.isEmpty() ) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) }, recodeVideoClicked = { downloadItem.videoPreferences.recodeVideo = it @@ -479,6 +486,16 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private } } } + + lifecycleScope.launch { + formatViewModel.noFreeSpace.collectLatest { + if (it != null) { + val snack = Snackbar.make(view, it, Snackbar.LENGTH_INDEFINITE) + snack.setTextMaxLines(10) + snack.show() + } + } + } } override fun updateTitleAuthor(t: String, a: String){ @@ -510,10 +527,11 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private downloadItem.videoPreferences.audioFormatIDs.clear() downloadItem.videoPreferences.audioFormatIDs.addAll(arrayListOf(format.format_id)) val formatCard = requireView().findViewById(R.id.format_card_constraintLayout) - UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, + val filesize = UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, if(downloadItem.videoPreferences.removeAudio) listOf() else listOf(format), showSize = downloadItem.downloadSections.isEmpty() ) + formatViewModel.checkFreeSpace(filesize, downloadItem.downloadPath) } private var pathResultLauncher = registerForActivityResult( diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt index 9965a45f..8cbb36fb 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt @@ -108,7 +108,8 @@ import java.util.Locale object UiUtil { @SuppressLint("SetTextI18n") - fun populateFormatCard(context: Context, formatCard : MaterialCardView, chosenFormat: Format, audioFormats: List? = null, showSize: Boolean = true){ + //return filesize + fun populateFormatCard(context: Context, formatCard : MaterialCardView, chosenFormat: Format, audioFormats: List? = null, showSize: Boolean = true) : Long { var formatNote = chosenFormat.format_note if (formatNote.isEmpty()) formatNote = context.getString(R.string.defaultValue) else if (formatNote == "best") formatNote = context.getString(R.string.best_quality) @@ -185,6 +186,12 @@ object UiUtil { } } + if (showSize) { + return filesize + }else { + return 0 + } + } fun populateCommandCard(card: MaterialCardView, item: CommandTemplate){ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 34b290a9..9a4d4fee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -485,4 +485,5 @@ When using both Embed Subtitles and Write Subtitles, don\'t keep the subtitle files after embedding Compatible Video Recodes the video making it compatible with other apps + There is no free space for this configuration.