From a388c5756367e0a08b229b67005393a6dba00fb3 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:27:31 -0600 Subject: [PATCH] Cleanup adapter, and rely more on view model + UI improvements + run more in IO context This makes it use the view model more when in downloads view, because otherwise when running savedData in an IO context it causes it to wait before updating progress which causes visible UI delays. This prevents that with downloads with the IsDone status. This also has a UI change: If a download is complete, it no longer shows bytes / bytes, it only shows bytes once. This is both a UI and performance improvement to have to do less. Finally, this does some cleanup to DownloadAdapter. --- .../ui/download/DownloadAdapter.kt | 196 ++++++++++-------- .../ui/download/button/BaseFetchButton.kt | 62 +++++- .../ui/download/button/PieFetchButton.kt | 3 +- app/src/main/res/values/strings.xml | 4 + 4 files changed, 164 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt index 317b378a..960644ff 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt @@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.download.button.DownloadStatusTell import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual @@ -93,112 +94,128 @@ class DownloadAdapter( private val mediaClickCallback: (DownloadClickEvent) -> Unit, ) : RecyclerView.ViewHolder(binding.root) { - @SuppressLint("SetTextI18n") fun bind(card: VisualDownloadCached?) { when (binding) { - is DownloadHeaderEpisodeBinding -> binding.apply { - if (card == null || card !is VisualDownloadHeaderCached) return@apply - val d = card.data + is DownloadHeaderEpisodeBinding -> bindHeader(card as? VisualDownloadHeaderCached) + is DownloadChildEpisodeBinding -> bindChild(card as? VisualDownloadChildCached) + } + } - downloadHeaderPoster.apply { - setImage(d.poster) - setOnClickListener { - clickCallback.invoke(DownloadHeaderClickEvent(1, d)) - } - } + @SuppressLint("SetTextI18n") + private fun bindHeader(card: VisualDownloadHeaderCached?) { + if (binding !is DownloadHeaderEpisodeBinding) return + card ?: return + val d = card.data - downloadHeaderTitle.text = d.name - val mbString = formatShortFileSize(itemView.context, card.totalBytes) - - if (card.child != null) { - downloadHeaderGotoChild.isVisible = false - - downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback) - downloadButton.setProgress(card.currentBytes, card.totalBytes) - downloadButton.isVisible = true - - episodeHolder.setOnClickListener { - mediaClickCallback.invoke( - DownloadClickEvent( - DOWNLOAD_ACTION_PLAY_FILE, - card.child - ) - ) - } - } else { - downloadButton.isVisible = false - downloadHeaderGotoChild.isVisible = true - - try { - downloadHeaderInfo.text = - downloadHeaderInfo.context.getString(R.string.extra_info_format) - .format( - card.totalDownloads, - if (card.totalDownloads == 1) downloadHeaderInfo.context.getString( - R.string.episode - ) else downloadHeaderInfo.context.getString( - R.string.episodes - ), - mbString - ) - } catch (t: Throwable) { - // You probably formatted incorrectly - downloadHeaderInfo.text = "Error" - logError(t) - } - - episodeHolder.setOnClickListener { - clickCallback.invoke(DownloadHeaderClickEvent(0, d)) - } + binding.apply { + downloadHeaderPoster.apply { + setImage(d.poster) + setOnClickListener { + clickCallback.invoke(DownloadHeaderClickEvent(1, d)) } } - is DownloadChildEpisodeBinding -> binding.apply { - if (card == null || card !is VisualDownloadChildCached) return@apply - val d = card.data + downloadHeaderTitle.text = d.name + val mbString = formatShortFileSize(itemView.context, card.totalBytes) - val posDur = DataStoreHelper.getViewPos(d.id) - downloadChildEpisodeProgress.apply { - if (posDur != null) { - val visualPos = posDur.fixVisual() - max = (visualPos.duration / 1000).toInt() - progress = (visualPos.position / 1000).toInt() - isVisible = true - } else isVisible = false + if (card.child != null) { + downloadHeaderGotoChild.isVisible = false + + val status = downloadButton.getStatus(card.child.id, card.currentBytes, card.totalBytes) + if (status == DownloadStatusTell.IsDone) { + // We do this here instead if we are finished downloading + // so that we can use the value from the view model + // rather than extra unneeded disk operations and to prevent a + // delay in updating download icon state. + downloadButton.setProgress(card.currentBytes, card.totalBytes) + downloadButton.applyMetaData(card.child.id, card.currentBytes, card.totalBytes) + // We will let the view model handle this + downloadButton.setProgressText = false + downloadHeaderInfo.text = formatShortFileSize(downloadHeaderInfo.context, card.totalBytes) + } else downloadButton.setProgressText = true + + downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback) + downloadButton.isVisible = true + + episodeHolder.setOnClickListener { + mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child)) + } + } else { + downloadButton.isVisible = false + downloadHeaderGotoChild.isVisible = true + + try { + downloadHeaderInfo.text = downloadHeaderInfo.context.getString(R.string.extra_info_format) + .format( + card.totalDownloads, + downloadHeaderInfo.context.resources.getQuantityString( + R.plurals.episodes, + card.totalDownloads + ), + mbString + ) + } catch (e: Exception) { + // You probably formatted incorrectly + downloadHeaderInfo.text = "Error" + logError(e) } - downloadButton.setDefaultClickListener(card.data, downloadChildEpisodeTextExtra, mediaClickCallback) + episodeHolder.setOnClickListener { + clickCallback.invoke(DownloadHeaderClickEvent(0, d)) + } + } + } + } + + private fun bindChild(card: VisualDownloadChildCached?) { + if (binding !is DownloadChildEpisodeBinding) return + card ?: return + val d = card.data + + binding.apply { + val posDur = DataStoreHelper.getViewPos(d.id) + downloadChildEpisodeProgress.apply { + isVisible = posDur != null + posDur?.let { + val visualPos = it.fixVisual() + max = (visualPos.duration / 1000).toInt() + progress = (visualPos.position / 1000).toInt() + } + } + + val status = downloadButton.getStatus(d.id, card.currentBytes, card.totalBytes) + if (status == DownloadStatusTell.IsDone) { + // We do this here instead if we are finished downloading + // so that we can use the value from the view model + // rather than extra unneeded disk operations and to prevent a + // delay in updating download icon state. downloadButton.setProgress(card.currentBytes, card.totalBytes) + downloadButton.applyMetaData(d.id, card.currentBytes, card.totalBytes) + // We will let the view model handle this + downloadButton.setProgressText = false + downloadChildEpisodeTextExtra.text = formatShortFileSize(downloadChildEpisodeTextExtra.context, card.totalBytes) + } else downloadButton.setProgressText = true - downloadChildEpisodeText.apply { - text = context.getNameFull(d.name, d.episode, d.season) - isSelected = true // Needed for text repeating - } + downloadButton.setDefaultClickListener(d, downloadChildEpisodeTextExtra, mediaClickCallback) + downloadButton.isVisible = true - downloadChildEpisodeHolder.setOnClickListener { - mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d)) - } + downloadChildEpisodeText.apply { + text = context.getNameFull(d.name, d.episode, d.season) + isSelected = true // Needed for text repeating + } + + downloadChildEpisodeHolder.setOnClickListener { + mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d)) } } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder { + val inflater = LayoutInflater.from(parent.context) val binding = when (viewType) { - VIEW_TYPE_HEADER -> { - DownloadHeaderEpisodeBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - } - VIEW_TYPE_CHILD -> { - DownloadChildEpisodeBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - } + VIEW_TYPE_HEADER -> DownloadHeaderEpisodeBinding.inflate(inflater, parent, false) + VIEW_TYPE_CHILD -> DownloadChildEpisodeBinding.inflate(inflater, parent, false) else -> throw IllegalArgumentException("Invalid view type") } return DownloadViewHolder(binding, clickCallback, mediaClickCallback) @@ -209,8 +226,11 @@ class DownloadAdapter( } override fun getItemViewType(position: Int): Int { - val card = getItem(position) - return if (card is VisualDownloadChildCached) VIEW_TYPE_CHILD else VIEW_TYPE_HEADER + return when (getItem(position)) { + is VisualDownloadChildCached -> VIEW_TYPE_CHILD + is VisualDownloadHeaderCached -> VIEW_TYPE_HEADER + else -> throw IllegalArgumentException("Invalid data type at position $position") + } } class DiffCallback : DiffUtil.ItemCallback() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt index 6dc1df2b..7d038b3f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt @@ -9,6 +9,8 @@ import androidx.annotation.LayoutRes import androidx.core.view.isVisible import androidx.core.widget.ContentLoadingProgressBar import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import com.lagradost.cloudstream3.utils.Coroutines.mainWork import com.lagradost.cloudstream3.utils.VideoDownloadManager typealias DownloadStatusTell = VideoDownloadManager.DownloadType @@ -64,11 +66,42 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : var currentMetaData: DownloadMetadata = DownloadMetadata(0, 0, 0, null) + private var progressSet = false + var setProgressText = true + + fun setPersistentId(id: Int) { + persistentId = id + currentMetaData.id = id + + if (progressSet) return + + ioSafe { + val savedData = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, id) + + mainWork { + if (savedData != null) { + val downloadedBytes = savedData.fileLength + val totalBytes = savedData.totalBytes + + setProgress(downloadedBytes, totalBytes) + applyMetaData(id, downloadedBytes, totalBytes) + } else run { resetView() } + } + } + } + abstract fun setStatus(status: VideoDownloadManager.DownloadType?) - open fun setProgress(downloadedBytes: Long, totalBytes: Long) { - val status = VideoDownloadManager.downloadStatus[id] - ?: if (downloadedBytes > 1024L && downloadedBytes + 1024L >= totalBytes) DownloadStatusTell.IsDone else DownloadStatusTell.IsPaused + fun getStatus(id:Int, downloadedBytes: Long, totalBytes: Long): DownloadStatusTell { + return VideoDownloadManager.downloadStatus[id] + ?: if (downloadedBytes > 1024L && downloadedBytes + 1024L >= totalBytes) { + DownloadStatusTell.IsDone + } else DownloadStatusTell.IsPaused + } + + fun applyMetaData(id:Int, downloadedBytes: Long, totalBytes: Long) { + val status = getStatus(id, downloadedBytes, totalBytes) + currentMetaData.apply { this.id = id this.downloadedLength = downloadedBytes @@ -76,6 +109,10 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : this.status = status } setStatus(status) + } + + open fun setProgress(downloadedBytes: Long, totalBytes: Long) { + progressSet = true isZeroBytes = downloadedBytes == 0L progressBar.post { val steps = 10000L @@ -100,13 +137,16 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : if (isZeroBytes) { progressText?.isVisible = false } else { - progressText?.apply { - val currentMbString = Formatter.formatShortFileSize(context, downloadedBytes) - val totalMbString = Formatter.formatShortFileSize(context, totalBytes) - text = - //if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else - context?.getString(R.string.download_size_format) - ?.format(currentMbString, totalMbString) + if (setProgressText) { + progressText?.apply { + val currentMbString = + Formatter.formatShortFileSize(context, downloadedBytes) + val totalMbString = Formatter.formatShortFileSize(context, totalBytes) + text = + //if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else + context?.getString(R.string.download_size_format) + ?.format(currentMbString, totalMbString) + } } } @@ -150,7 +190,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : val pid = persistentId if (pid != null) { // refresh in case of onDetachedFromWindow -> onAttachedToWindow while still being ??????? - currentMetaData.id = pid + setPersistentId(pid) } super.onAttachedToWindow() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt index f9b8a6ee..df5c5bb5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt @@ -13,7 +13,6 @@ import androidx.annotation.MainThread import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.core.view.isVisible -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError @@ -29,7 +28,6 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager.KEY_RESUME_PACKAGES - open class PieFetchButton(context: Context, attributeSet: AttributeSet) : BaseFetchButton(context, attributeSet) { @@ -167,6 +165,7 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) : callback: (DownloadClickEvent) -> Unit ) { this.progressText = textView + this.setPersistentId(card.id) view.setOnClickListener { if (isZeroBytes) { removeKey(KEY_RESUME_PACKAGES, card.id.toString()) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f577d6e1..f5f8d40a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -339,6 +339,10 @@ Livestreams NSFW Others + + Episode + Episodes + Movie Series