From 3ceabb4de3e1e236dae1ec824b7a68cec599c536 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:39:52 -0600 Subject: [PATCH] Minor performance optimization and split to new class file and cleanup --- .../ui/download/DownloadFragment.kt | 165 +---------------- .../download/DownloadSwipeToDeleteCallback.kt | 173 ++++++++++++++++++ 2 files changed, 179 insertions(+), 159 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadSwipeToDeleteCallback.kt 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 767e4906..89979dfd 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 @@ -4,23 +4,15 @@ import android.annotation.SuppressLint import android.app.Dialog import android.content.ClipboardManager 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.os.Build import android.os.Bundle import android.text.format.Formatter.formatShortFileSize import android.view.LayoutInflater -import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged @@ -32,7 +24,6 @@ import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding import com.lagradost.cloudstream3.databinding.StreamInputBinding -import com.lagradost.cloudstream3.isEpisodeBased import com.lagradost.cloudstream3.isMovieType import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.observe @@ -46,7 +37,6 @@ import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.Coroutines.main -import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE import com.lagradost.cloudstream3.utils.DataStore import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe @@ -215,7 +205,12 @@ class DownloadFragment : Fragment() { ) //layoutManager = GridLayoutManager(context, 1) - val itemTouchHelper = ItemTouchHelper(SwipeToDeleteCallback(this.adapter as DownloadHeaderAdapter)) + val itemTouchHelper = ItemTouchHelper( + DownloadSwipeToDeleteCallback( + this.adapter as DownloadHeaderAdapter, + context + ) + ) itemTouchHelper.attachToRecyclerView(binding?.downloadList) } @@ -297,152 +292,4 @@ class DownloadFragment : Fragment() { fixPaddingStatusbar(binding?.downloadRoot) } -} - -class SwipeToDeleteCallback(private val adapter: DownloadHeaderAdapter) : ItemTouchHelper.Callback() { - - private val swipeOpenItems: MutableSet = mutableSetOf() - - override fun getMovementFlags( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder - ): Int { - val position = viewHolder.bindingAdapterPosition - val item = adapter.cardList[position] - if (item.data.type.isEpisodeBased()) return 0 - return makeMovementFlags(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) - } - - 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 - ) {} - - private fun handleDelete(position: Int) { - val item = adapter.cardList[position] - - runOnMainThread { - item.child?.let { clickEvent -> - handleDownloadClick( - DownloadClickEvent( - DOWNLOAD_ACTION_DELETE_FILE, - clickEvent - ) - ) { adapter.notifyItemRemoved(position) } - } - } - } - - @SuppressLint("ClickableViewAccessibility") - 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 position = viewHolder.bindingAdapterPosition - - val deleteIcon: Drawable = ContextCompat.getDrawable( - recyclerView.context, - R.drawable.ic_baseline_delete_outline_24 - ) ?: return - - val background = ColorDrawable(Color.RED) - background.alpha = 160 - - val itemView = viewHolder.itemView - - val scaleFactor = 1.25f - val iconWidth = (deleteIcon.intrinsicWidth * scaleFactor).toInt() - val iconHeight = (deleteIcon.intrinsicHeight * scaleFactor).toInt() - - val iconTop = itemView.top + (itemView.height - iconHeight) / 2 - val iconBottom = iconTop + iconHeight - - val maxSwipeDistance = 230f - val minSwipeDistance = itemView.width / 4.5f - val swipeDistance = minOf(minSwipeDistance, maxSwipeDistance) - - val limitedDX = if (dX < -swipeDistance) -swipeDistance else if (dX >= 0) 0f else dX - - if (limitedDX < 0) { // Swiping to the left - val backgroundLeft = itemView.right + limitedDX.toInt() - val backgroundRight = itemView.right - val backgroundTop = itemView.top - val backgroundBottom = itemView.bottom - - val iconLeft = backgroundLeft + (backgroundRight - backgroundLeft - iconWidth) / 2 - val iconRight = iconLeft + iconWidth - deleteIcon.setBounds(iconLeft, iconTop, iconRight, iconBottom) - - val path = Path() - val rectF = RectF( - backgroundLeft.toFloat(), - backgroundTop.toFloat(), - backgroundRight.toFloat(), - backgroundBottom.toFloat() - ) - - val radii = floatArrayOf(0f, 0f, 20f, 20f, 20f, 20f, 0f, 0f) - path.addRoundRect(rectF, radii, Path.Direction.CW) - c.clipPath(path) - - background.setBounds(backgroundLeft, backgroundTop, backgroundRight, backgroundBottom) - } else background.setBounds(0, 0, 0, 0) - - background.draw(c) - deleteIcon.draw(c) - - if (dX <= -swipeDistance && !isCurrentlyActive) { - swipeOpenItems.add(position) - } else { - swipeOpenItems.remove(position) - super.onChildDraw(c, recyclerView, viewHolder, limitedDX, dY, actionState, isCurrentlyActive) - } - - if (swipeOpenItems.isNotEmpty()) { - recyclerView.setOnTouchListener { _, event -> - if (event.action == MotionEvent.ACTION_UP) { - val x = event.x.toInt() - val y = event.y.toInt() - var handled = false - - swipeOpenItems.forEach { pos -> - val vh = recyclerView.findViewHolderForAdapterPosition(pos) - if (vh != null) { - val swipeItemView = vh.itemView - val backgroundLeft = swipeItemView.right - swipeDistance.toInt() - val backgroundRight = swipeItemView.right - val backgroundTop = swipeItemView.top - val backgroundBottom = swipeItemView.bottom - - if (x in backgroundLeft..backgroundRight && y in backgroundTop..backgroundBottom) { - handleDelete(pos) - handled = true - } - } - } - handled - } else false - } - } else recyclerView.setOnTouchListener(null) - } } \ 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/DownloadSwipeToDeleteCallback.kt new file mode 100644 index 00000000..c3f088eb --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadSwipeToDeleteCallback.kt @@ -0,0 +1,173 @@ +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.utils.Coroutines.runOnMainThread + +class DownloadSwipeToDeleteCallback( + private val adapter: DownloadHeaderAdapter, + private val context: Context +) : ItemTouchHelper.Callback() { + + private val swipeOpenItems: MutableSet = 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 position = viewHolder.bindingAdapterPosition + val item = adapter.cardList[position] + return if (item.data.type.isEpisodeBased()) 0 else { + makeMovementFlags(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) + } + } + + 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 + + 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, 20f, 20f, 20f, 20f, 0f, 0f), + 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) { + swipeOpenItems.add(viewHolder.bindingAdapterPosition) + setRecyclerViewTouchListener(recyclerView, swipeDistance) + } else { + swipeOpenItems.remove(viewHolder.bindingAdapterPosition) + if (swipeOpenItems.isEmpty()) removeRecyclerViewTouchListener(recyclerView) + super.onChildDraw(c, recyclerView, viewHolder, limitedDX, dY, actionState, isCurrentlyActive) + } + } + + @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) { + handleDelete(pos) + return@setOnTouchListener true + } + } + } + false + } else false + } + } + + @SuppressLint("ClickableViewAccessibility") + private fun removeRecyclerViewTouchListener( + recyclerView: RecyclerView + ): Unit = recyclerView.setOnTouchListener(null) + + private fun handleDelete(position: Int) { + val item = adapter.cardList[position] + runOnMainThread { + item.child?.let { clickEvent -> + DownloadButtonSetup.handleDownloadClick( + DownloadClickEvent( + DOWNLOAD_ACTION_DELETE_FILE, + clickEvent + ) + ) { adapter.notifyItemRemoved(position) } + } + } + } +} \ No newline at end of file