diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt index 63a268ed..10ce67a7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt @@ -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 -> { } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index 89979dfd..f0036351 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -66,7 +66,7 @@ class DownloadFragment : Fragment() { @SuppressLint("NotifyDataSetChanged") private fun setList(list: List) { 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 ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt index 65a6441f..749f0da0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt @@ -29,7 +29,7 @@ data class DownloadHeaderClickEvent( ) class DownloadHeaderAdapter( - var cardList: List, + var cardList: MutableList, private val clickCallback: (DownloadHeaderClickEvent) -> Unit, private val movieClickCallback: (DownloadClickEvent) -> Unit, ) : RecyclerView.Adapter() { @@ -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 { @@ -146,4 +146,4 @@ class DownloadHeaderAdapter( } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadSwipeToDeleteCallback.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadSwipeDeleteCallback.kt similarity index 61% rename from app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadSwipeToDeleteCallback.kt rename to app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadSwipeDeleteCallback.kt index 14652d25..ef6b5ea6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadSwipeToDeleteCallback.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadSwipeDeleteCallback.kt @@ -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 = 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 + } + } } \ No newline at end of file