Add swipe to delete downloads and minor performance improvements

This could honestly be a horrible way to impliment this (or maybe you don't want it at all) but figured I would give it a try as I often am used to you being able to on other apps so often out of habbit try like this and of course does not work.

This also seems to fix an issue where downloaded icon becomes mixed with in progress and downloaded overlapping on scroll if you have inprogress downloads as well as size becoming inaccurate while scrolling (it used the wrong file's size sometimes) which seems to be fixed with `setItemViewCacheSize`. `setHasFixedSize` also improves performance by reducing the need to continue re-requesting layout when it does not need to (testing did not show any issues with it anyway, but not sure if this is the best way to do either of these things.

Known issues:
* Does not work for series/individual episodes
* Delete icon UI needs improvements

TODO (for further download improvements; maybe in follow-ups):
* Show more information (IE synopsis/descriptions) for downloaded content
* Further performance improvements for UI threads etc...


If the entire concept of this idea is undesired I suppose we can close it and I will just do some of the other things to improve performance etc... (IE when having a moderate amount of downloads scroll is very laggy which this also seems to at least improve quite a lot.
This commit is contained in:
Luna712 2024-06-08 13:56:32 -06:00 committed by GitHub
parent 7eec0eff02
commit d0710596d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 102 additions and 2 deletions

View file

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.ui.download package com.lagradost.cloudstream3.ui.download
import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@ -21,6 +20,10 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager
object DownloadButtonSetup { object DownloadButtonSetup {
fun handleDownloadClick(click: DownloadClickEvent) { fun handleDownloadClick(click: DownloadClickEvent) {
handleDownloadClick(click) {}
}
fun handleDownloadClick(click: DownloadClickEvent, deleteCallback: (Boolean) -> Unit) {
val id = click.data.id val id = click.data.id
if (click.data !is VideoDownloadHelper.DownloadEpisodeCached) return if (click.data !is VideoDownloadHelper.DownloadEpisodeCached) return
when (click.action) { when (click.action) {
@ -32,8 +35,10 @@ object DownloadButtonSetup {
when (which) { when (which) {
DialogInterface.BUTTON_POSITIVE -> { DialogInterface.BUTTON_POSITIVE -> {
VideoDownloadManager.deleteFileAndUpdateSettings(ctx, id) VideoDownloadManager.deleteFileAndUpdateSettings(ctx, id)
deleteCallback.invoke(true)
} }
DialogInterface.BUTTON_NEGATIVE -> { DialogInterface.BUTTON_NEGATIVE -> {
deleteCallback.invoke(false)
} }
} }
} }

View file

@ -1,8 +1,13 @@
package com.lagradost.cloudstream3.ui.download package com.lagradost.cloudstream3.ui.download
import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.format.Formatter.formatShortFileSize import android.text.format.Formatter.formatShortFileSize
@ -12,11 +17,13 @@ import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
@ -44,6 +51,9 @@ import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.URI import java.net.URI
@ -61,6 +71,7 @@ class DownloadFragment : Fragment() {
this.layoutParams = param this.layoutParams = param
} }
@SuppressLint("NotifyDataSetChanged")
private fun setList(list: List<VisualDownloadHeaderCached>) { private fun setList(list: List<VisualDownloadHeaderCached>) {
main { main {
(binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList = list (binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList = list
@ -182,7 +193,6 @@ class DownloadFragment : Fragment() {
if (list != null) { if (list != null) {
if (list.any { it.data.id == id }) { if (list.any { it.data.id == id }) {
context?.let { ctx -> context?.let { ctx ->
setList(ArrayList())
downloadsViewModel.updateList(ctx) downloadsViewModel.updateList(ctx)
} }
} }
@ -192,6 +202,8 @@ class DownloadFragment : Fragment() {
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it } downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
binding?.downloadList?.apply { binding?.downloadList?.apply {
setHasFixedSize(true)
setItemViewCacheSize(20)
this.adapter = adapter this.adapter = adapter
setLinearListLayout( setLinearListLayout(
isHorizontal = false, isHorizontal = false,
@ -200,6 +212,9 @@ class DownloadFragment : Fragment() {
nextDown = FOCUS_SELF nextDown = FOCUS_SELF
) )
//layoutManager = GridLayoutManager(context, 1) //layoutManager = GridLayoutManager(context, 1)
val itemTouchHelper = ItemTouchHelper(SwipeToDeleteCallback(this.adapter as DownloadHeaderAdapter))
itemTouchHelper.attachToRecyclerView(binding?.downloadList)
} }
// Should be visible in emulator layout // Should be visible in emulator layout
@ -281,3 +296,83 @@ class DownloadFragment : Fragment() {
fixPaddingStatusbar(binding?.downloadRoot) fixPaddingStatusbar(binding?.downloadRoot)
} }
} }
class SwipeToDeleteCallback(private val adapter: DownloadHeaderAdapter) :
ItemTouchHelper.SimpleCallback(ItemTouchHelper.LEFT, 0) {
private var isActive = false
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.bindingAdapterPosition
val item = adapter.cardList[position]
CoroutineScope(Dispatchers.Main).launch {
item.child?.let { clickEvent ->
handleDownloadClick(
DownloadClickEvent(
DOWNLOAD_ACTION_DELETE_FILE,
clickEvent
)
) {
if (it) adapter.notifyItemRemoved(viewHolder.absoluteAdapterPosition)
isActive = false
}
}
}
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
val deleteIcon: Drawable? = ContextCompat.getDrawable(recyclerView.context, R.drawable.ic_baseline_delete_outline_24)
val background: ColorDrawable = ColorDrawable(Color.RED)
val itemView = viewHolder.itemView
val backgroundCornerOffset = 20
val iconMargin = (itemView.height - deleteIcon?.intrinsicHeight!!) / 2
val iconTop = itemView.top + (itemView.height - deleteIcon.intrinsicHeight) / 2
val iconBottom = iconTop + deleteIcon.intrinsicHeight
if (dX < 0) { // Swiping to the left
val iconLeft = itemView.right - iconMargin - deleteIcon.intrinsicWidth
val iconRight = itemView.right - iconMargin
deleteIcon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
background.setBounds(
itemView.right + dX.toInt() - backgroundCornerOffset,
itemView.top,
itemView.right,
itemView.bottom
)
} else background.setBounds(0, 0, 0, 0)
background.draw(c)
deleteIcon.draw(c)
val swipeDistance = itemView.width / 3
if (dX <= -swipeDistance && !isCurrentlyActive && !isActive) {
// If the item is dragged by at least one-third of its width to the left
// Trigger onSwiped action
onSwiped(viewHolder, ItemTouchHelper.LEFT)
isActive = true
}
}
}