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