mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
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.
This commit is contained in:
parent
0b69793d7c
commit
a388c57563
4 changed files with 164 additions and 101 deletions
|
@ -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<VisualDownloadCached>() {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -339,6 +339,10 @@
|
|||
<string name="livestreams">Livestreams</string>
|
||||
<string name="nsfw">NSFW</string>
|
||||
<string name="others">Others</string>
|
||||
<plurals name="episodes">
|
||||
<item quantity="one">Episode</item>
|
||||
<item quantity="other">Episodes</item>
|
||||
</plurals>
|
||||
<!--singular-->
|
||||
<string name="movies_singular">Movie</string>
|
||||
<string name="tv_series_singular">Series</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue