Swipe delete without reloading all of the cards and add some comments and other cleanup

We can use adapter.notifyItemRemoved and remove it from the cardList in adapater directly since we do understand the position in this case. It works better because it does not require reloading everything and causes less bugs with the view model.

I am not sure if this was the proper way to do this. If not I can revert the change to this method

It also removes the need for a callback in DownloadButtonSetup
This commit is contained in:
Luna712 2024-06-14 20:15:36 -06:00 committed by GitHub
parent 312c2a738b
commit baaf9320e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 106 additions and 37 deletions

View file

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

View file

@ -66,7 +66,7 @@ class DownloadFragment : Fragment() {
@SuppressLint("NotifyDataSetChanged")
private fun setList(list: List<VisualDownloadHeaderCached>) {
main {
(binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList = list
(binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList = list.toMutableList()
binding?.downloadList?.adapter?.notifyDataSetChanged()
}
}
@ -147,7 +147,7 @@ class DownloadFragment : Fragment() {
when (click.action) {
0 -> {
if (click.data.type.isMovieType()) {
//wont be called
// Won't be called
} else {
val folder = DataStore.getFolderName(
DOWNLOAD_EPISODE_CACHE,
@ -173,26 +173,21 @@ class DownloadFragment : Fragment() {
if (downloadClickEvent.data !is VideoDownloadHelper.DownloadEpisodeCached) return@DownloadHeaderAdapter
handleDownloadClick(downloadClickEvent)
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
context?.let { ctx ->
downloadsViewModel.updateList(ctx)
downloadDeleteEventListener = { id ->
val list = (binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList
if (list != null) {
if (list.any { it.data.id == id }) {
context?.let { ctx ->
downloadsViewModel.updateList(ctx)
}
}
}
}
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
}
}
)
downloadDeleteEventListener = { id ->
val list = (binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList
if (list != null) {
if (list.any { it.data.id == id }) {
context?.let { ctx ->
downloadsViewModel.updateList(ctx)
}
}
}
}
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
binding?.downloadList?.apply {
setHasFixedSize(true)
setItemViewCacheSize(20)
@ -203,10 +198,9 @@ class DownloadFragment : Fragment() {
nextUp = FOCUS_SELF,
nextDown = FOCUS_SELF
)
//layoutManager = GridLayoutManager(context, 1)
val itemTouchHelper = ItemTouchHelper(
DownloadSwipeToDeleteCallback(
DownloadSwipeDeleteCallback(
this.adapter as DownloadHeaderAdapter,
context
)

View file

@ -29,7 +29,7 @@ data class DownloadHeaderClickEvent(
)
class DownloadHeaderAdapter(
var cardList: List<VisualDownloadHeaderCached>,
var cardList: MutableList<VisualDownloadHeaderCached>,
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
private val movieClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@ -55,7 +55,7 @@ class DownloadHeaderAdapter(
}
override fun getItemCount(): Int {
return cardList.size
return cardList.count()
}
class DownloadHeaderViewHolder
@ -94,7 +94,7 @@ class DownloadHeaderAdapter(
if (card.child != null) {
//downloadHeaderProgressDownloaded.visibility = View.VISIBLE
// downloadHeaderEpisodeDownload.visibility = View.VISIBLE
// downloadHeaderEpisodeDownload.visibility = View.VISIBLE
binding.downloadHeaderGotoChild.visibility = View.GONE
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, movieClickCallback)
@ -119,8 +119,8 @@ class DownloadHeaderAdapter(
}
} else {
downloadButton.isVisible = false
// downloadHeaderProgressDownloaded.visibility = View.GONE
// downloadHeaderEpisodeDownload.visibility = View.GONE
// downloadHeaderProgressDownloaded.visibility = View.GONE
// downloadHeaderEpisodeDownload.visibility = View.GONE
binding.downloadHeaderGotoChild.visibility = View.VISIBLE
try {

View file

@ -16,12 +16,15 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.isEpisodeBased
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadDeleteEvent
class DownloadSwipeToDeleteCallback(
class DownloadSwipeDeleteCallback(
private val adapter: DownloadHeaderAdapter,
private val context: Context
) : ItemTouchHelper.Callback() {
private var downloadDeleteEventListener: ((Int) -> Unit)? = null
private val swipeOpenItems: MutableSet<Int> = mutableSetOf()
private val deleteIcon: Drawable? by lazy {
ContextCompat.getDrawable(context, R.drawable.ic_baseline_delete_outline_24)
@ -39,7 +42,8 @@ class DownloadSwipeToDeleteCallback(
val position = viewHolder.bindingAdapterPosition
val item = adapter.cardList[position]
return if (item.data.type.isEpisodeBased()) 0 else {
makeMovementFlags(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT)
val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
makeMovementFlags(0, swipeFlags)
}
}
@ -102,7 +106,12 @@ class DownloadSwipeToDeleteCallback(
itemView.right.toFloat(),
itemView.bottom.toFloat()
),
floatArrayOf(0f, 0f, 20f, 20f, 20f, 20f, 0f, 0f),
floatArrayOf(
0f, 0f, // Top-left corner
20f, 20f, // Top-right corner
20f, 20f, // Bottom-right corner
0f, 0f // Bottom-left corner
),
Path.Direction.CW
)
}
@ -127,6 +136,14 @@ class DownloadSwipeToDeleteCallback(
}
}
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
clearDownloadDeleteEvent()
super.clearView(recyclerView, viewHolder)
}
@SuppressLint("ClickableViewAccessibility")
private fun setRecyclerViewTouchListener(
recyclerView: RecyclerView,
@ -136,14 +153,17 @@ class DownloadSwipeToDeleteCallback(
if (event.action == MotionEvent.ACTION_UP) {
val x = event.x.toInt()
val y = event.y.toInt()
swipeOpenItems.forEach { pos ->
// We use toList() to avoid a very rare edge case
// where it gives concurrent modification errors
swipeOpenItems.toList().forEach { pos ->
val vh = recyclerView.findViewHolderForAdapterPosition(pos)
vh?.itemView?.let { swipeItemView ->
val backgroundLeft: Int = swipeItemView.right - swipeDistance.toInt()
val backgroundXRange: IntRange = backgroundLeft..swipeItemView.right
val backgroundYRange: IntRange = swipeItemView.top..swipeItemView.bottom
if (x in backgroundXRange && y in backgroundYRange) {
handleDelete(pos)
handleDeleteAction(pos)
addDownloadDeleteEvent(pos, recyclerView)
return@setOnTouchListener true
}
}
@ -154,11 +174,19 @@ class DownloadSwipeToDeleteCallback(
}
@SuppressLint("ClickableViewAccessibility")
private fun removeRecyclerViewTouchListener(
recyclerView: RecyclerView
): Unit = recyclerView.setOnTouchListener(null)
private fun removeRecyclerViewTouchListener(recyclerView: RecyclerView) {
// We don't want to unnecessarily listen to unused touch events
recyclerView.setOnTouchListener(null)
private fun handleDelete(position: Int) {
/**
* If we are not listening to touch events, then
* we should clear the delete event as it will
* not be used at the moment.
*/
clearDownloadDeleteEvent()
}
private fun handleDeleteAction(position: Int) {
val item = adapter.cardList[position]
runOnMainThread {
item.child?.let { clickEvent ->
@ -167,8 +195,55 @@ class DownloadSwipeToDeleteCallback(
DOWNLOAD_ACTION_DELETE_FILE,
clickEvent
)
) { adapter.notifyItemRemoved(position) }
)
}
}
}
private fun addDownloadDeleteEvent(
position: Int,
recyclerView: RecyclerView
) {
// Clear any old events as we don't want to get
// concurrent modification errors
clearDownloadDeleteEvent()
downloadDeleteEventListener = { id: Int ->
val list = adapter.cardList
if (list.any { it.data.id == id }) {
/**
* Seamlessly remove now-deleted item from adapter.
* We don't need to reload from the viewModel,
* that just causes more unnecessary actions and
* unreliable data to be returned oftentimes,
* as it would cause it to reload the entire
* view model (which is all items) we only want
* to target one item and this provides a more seamless
* and performant solution to it since we do have access to
* the position we need to target here.
*/
adapter.cardList.removeAt(position)
adapter.notifyItemRemoved(position)
adapter.notifyItemRangeChanged(
position,
adapter.cardList.size
) // rebind to new positions
/**
* we need to clear the listener now since nothing should be open
* and it was closed outside of on onChildDraw so we don't
* want to have random unexpected touch events.
*/
removeRecyclerViewTouchListener(recyclerView)
}
}
downloadDeleteEventListener?.let { downloadDeleteEvent += it }
}
private fun clearDownloadDeleteEvent() {
if (downloadDeleteEventListener != null) {
downloadDeleteEvent -= downloadDeleteEventListener!!
downloadDeleteEventListener = null
}
}
}