Remove swipe to delete

This commit is contained in:
Luna712 2024-06-19 11:02:55 -06:00 committed by GitHub
parent 431a7e543f
commit fa14b5e17f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 0 additions and 284 deletions

View file

@ -6,7 +6,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding
@ -131,16 +130,6 @@ class DownloadChildFragment : Fragment() {
nextRight = FOCUS_SELF, nextRight = FOCUS_SELF,
nextDown = FOCUS_SELF nextDown = FOCUS_SELF
) )
if (isLayout(PHONE or EMULATOR)) {
val itemTouchHelper = ItemTouchHelper(
DownloadSwipeDeleteCallback(
this.adapter as DownloadAdapter,
context ?: return@apply
)
)
itemTouchHelper.attachToRecyclerView(binding?.downloadChildList)
}
} }
updateList(folder) updateList(folder)

View file

@ -18,7 +18,6 @@ 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
@ -33,8 +32,6 @@ import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.LinkGenerator import com.lagradost.cloudstream3.ui.player.LinkGenerator
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadResult
@ -198,16 +195,6 @@ class DownloadFragment : Fragment() {
nextUp = FOCUS_SELF, nextUp = FOCUS_SELF,
nextDown = FOCUS_SELF nextDown = FOCUS_SELF
) )
if (isLayout(PHONE or EMULATOR)) {
val itemTouchHelper = ItemTouchHelper(
DownloadSwipeDeleteCallback(
this.adapter as DownloadAdapter,
context
)
)
itemTouchHelper.attachToRecyclerView(binding?.downloadList)
}
} }
// Should be visible in emulator layout // Should be visible in emulator layout

View file

@ -1,260 +0,0 @@
package com.lagradost.cloudstream3.ui.download
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Path
import android.graphics.RectF
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.view.MotionEvent
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
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.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadDeleteEvent
class DownloadSwipeDeleteCallback(
private val adapter: DownloadAdapter,
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)
}
private val background: ColorDrawable by lazy {
ColorDrawable(Color.RED).apply { alpha = 160 }
}
private val scaleFactor = 1.25f
private val maxSwipeDistance = 230f
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
val position = viewHolder.bindingAdapterPosition
val item = adapter.cardList[position]
if (item !is VisualDownloadHeaderCached) return makeMovementFlags(0, swipeFlags)
return if (item.data.type.isEpisodeBased()) 0 else {
makeMovementFlags(0, swipeFlags)
}
}
override fun isLongPressDragEnabled(): Boolean = false
override fun isItemViewSwipeEnabled(): Boolean = true
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean = false
override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
direction: Int
) {}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
if (dX == 0f) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
return
}
val itemView = viewHolder.itemView
val minSwipeDistance = itemView.width / 4.5f
val swipeDistance = minOf(minSwipeDistance, maxSwipeDistance)
val limitedDX = if (dX < -swipeDistance) -swipeDistance else if (dX >= 0) 0f else dX
val position = viewHolder.bindingAdapterPosition
if (swipeOpenItems.contains(position)) {
// If the item is already swiped we need to restore that
// state so that you can delete items without the state
// resetting, making it easier to quickly delete multiple items.
super.onChildDraw(c, recyclerView, viewHolder, limitedDX, dY, actionState, isCurrentlyActive)
}
if (limitedDX < 0) { // Swiping to the left
val icon = deleteIcon ?: return
val backgroundLeft = itemView.right + limitedDX.toInt()
val iconWidth = (icon.intrinsicWidth * scaleFactor).toInt()
val iconHeight = (icon.intrinsicHeight * scaleFactor).toInt()
val iconTop = itemView.top + (itemView.height - iconHeight) / 2
val iconBottom = iconTop + iconHeight
val iconLeft = backgroundLeft + (itemView.right - backgroundLeft - iconWidth) / 2
val iconRight = iconLeft + iconWidth
icon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
val path = Path().apply {
addRoundRect(
RectF(
backgroundLeft.toFloat(),
itemView.top.toFloat(),
itemView.right.toFloat(),
itemView.bottom.toFloat()
),
floatArrayOf(
0f, 0f, // Top-left corner
20f, 20f, // Top-right corner
20f, 20f, // Bottom-right corner
0f, 0f // Bottom-left corner
),
Path.Direction.CW
)
}
c.clipPath(path)
background.setBounds(
backgroundLeft,
itemView.top,
itemView.right,
itemView.bottom
)
background.draw(c)
icon.draw(c)
} else background.setBounds(0, 0, 0, 0)
if (dX <= -swipeDistance && !isCurrentlyActive && adapter.cardList.getOrNull(position) != null) {
swipeOpenItems.add(position)
setRecyclerViewTouchListener(recyclerView, swipeDistance)
} else {
swipeOpenItems.remove(position)
if (swipeOpenItems.isEmpty()) removeRecyclerViewTouchListener(recyclerView)
super.onChildDraw(c, recyclerView, viewHolder, limitedDX, dY, actionState, isCurrentlyActive)
}
}
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
clearDownloadDeleteEvent()
super.clearView(recyclerView, viewHolder)
}
@SuppressLint("ClickableViewAccessibility")
private fun setRecyclerViewTouchListener(
recyclerView: RecyclerView,
swipeDistance: Float
) {
recyclerView.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP) {
val x = event.x.toInt()
val y = event.y.toInt()
swipeOpenItems.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) {
handleDeleteAction(pos)
addDownloadDeleteEvent(pos)
return@setOnTouchListener true
}
}
}
false
} else false
}
}
@SuppressLint("ClickableViewAccessibility")
private fun removeRecyclerViewTouchListener(recyclerView: RecyclerView) {
// We don't want to unnecessarily listen to unused touch events
recyclerView.setOnTouchListener(null)
/**
* 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 {
val data: VideoDownloadHelper.DownloadEpisodeCached? = if (item is VisualDownloadHeaderCached) item.child else {
item.data as VideoDownloadHelper.DownloadEpisodeCached?
}
data?.let { clickEvent ->
handleDownloadClick(
DownloadClickEvent(
DOWNLOAD_ACTION_DELETE_FILE,
clickEvent
)
)
}
}
}
private fun addDownloadDeleteEvent(position: Int) {
// 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.
*/
if (list.getOrNull(position) != null) {
adapter.cardList.removeAt(position)
}
adapter.notifyItemRemoved(position)
}
}
// We use synchronized to ensure we are thread-safe and
// to avoid potential race conditions that may cause
// concurrent modification errors
synchronized(this) {
downloadDeleteEventListener?.let { downloadDeleteEvent += it }
}
}
private fun clearDownloadDeleteEvent() {
// We use synchronized to ensure we are thread-safe and
// to avoid potential race conditions that may cause
// concurrent modification errors
synchronized(this) {
if (downloadDeleteEventListener != null) {
downloadDeleteEvent -= downloadDeleteEventListener!!
downloadDeleteEventListener = null
}
}
}
}