From 2122677663468b5ff4d29f0c0e1d836df732ac5c Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 20 Jun 2024 18:33:22 -0600 Subject: [PATCH] Rewrite to use DiffUtil and massive cleanup --- .../ui/download/DownloadAdapter.kt | 85 +++--- .../ui/download/DownloadChildFragment.kt | 47 ++- .../ui/download/DownloadFragment.kt | 272 ++++++++---------- 3 files changed, 189 insertions(+), 215 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt index 5473e0d2..6825ab12 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt @@ -5,6 +5,8 @@ import android.text.format.Formatter.formatShortFileSize import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import com.lagradost.cloudstream3.R @@ -17,7 +19,6 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper - const val DOWNLOAD_ACTION_PLAY_FILE = 0 const val DOWNLOAD_ACTION_DELETE_FILE = 1 const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2 @@ -29,7 +30,20 @@ abstract class VisualDownloadCached( open val currentBytes: Long, open val totalBytes: Long, open val data: VideoDownloadHelper.DownloadCached -) +) { + + // Just to be extra-safe with areContentsTheSame + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is VisualDownloadCached) return false + + if (currentBytes != other.currentBytes) return false + if (totalBytes != other.totalBytes) return false + if (data != other.data) return false + + return true + } +} data class VisualDownloadChildCached( override val currentBytes: Long, @@ -57,10 +71,9 @@ data class DownloadHeaderClickEvent( ) class DownloadAdapter( - var cardList: List, private val clickCallback: (DownloadHeaderClickEvent) -> Unit, private val mediaClickCallback: (DownloadClickEvent) -> Unit, -) : RecyclerView.Adapter() { +) : ListAdapter(DiffCallback()) { companion object { private const val VIEW_TYPE_HEADER = 0 @@ -161,45 +174,43 @@ class DownloadAdapter( } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder = - DownloadViewHolder( - binding = when (viewType) { - VIEW_TYPE_HEADER -> { - DownloadHeaderEpisodeBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - } - VIEW_TYPE_CHILD -> { - DownloadChildEpisodeBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - } - else -> throw IllegalArgumentException("Invalid view type") - }, - clickCallback, - mediaClickCallback - ) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder { + val binding = when (viewType) { + VIEW_TYPE_HEADER -> { + DownloadHeaderEpisodeBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + } + VIEW_TYPE_CHILD -> { + DownloadChildEpisodeBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + } + else -> throw IllegalArgumentException("Invalid view type") + } + return DownloadViewHolder(binding, clickCallback, mediaClickCallback) + } override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) { - holder.bind(cardList.getOrNull(position)) + holder.bind(getItem(position)) } - var viewType = 0 - override fun getItemViewType(position: Int): Int { - if (viewType != 0) return viewType - - val card = cardList.getOrNull(position) ?: return 0 - - val isChildView = card is VisualDownloadChildCached - return if (isChildView) VIEW_TYPE_CHILD else VIEW_TYPE_HEADER + val card = getItem(position) + return if (card is VisualDownloadChildCached) VIEW_TYPE_CHILD else VIEW_TYPE_HEADER } - override fun getItemCount(): Int { - return cardList.count() + class DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: VisualDownloadCached, newItem: VisualDownloadCached): Boolean { + return oldItem.data.id == newItem.data.id + } + + override fun areContentsTheSame(oldItem: VisualDownloadCached, newItem: VisualDownloadCached): Boolean { + return oldItem == newItem + } } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt index 53e0bccb..7734cb08 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt @@ -1,12 +1,10 @@ package com.lagradost.cloudstream3.ui.download -import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick @@ -41,7 +39,8 @@ class DownloadChildFragment : Fragment() { super.onDestroyView() } - var binding: FragmentChildDownloadsBinding? = null + private var binding: FragmentChildDownloadsBinding? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -49,10 +48,9 @@ class DownloadChildFragment : Fragment() { ): View { val localBinding = FragmentChildDownloadsBinding.inflate(inflater, container, false) binding = localBinding - return localBinding.root//inflater.inflate(R.layout.fragment_child_downloads, container, false) + return localBinding.root } - @SuppressLint("NotifyDataSetChanged") private fun updateList(folder: String) = main { context?.let { ctx -> val data = withContext(Dispatchers.IO) { ctx.getKeys(folder) } @@ -74,9 +72,7 @@ class DownloadChildFragment : Fragment() { return@main } - (binding?.downloadChildList?.adapter as DownloadAdapter? ?: return@main).cardList = - eps - binding?.downloadChildList?.adapter?.notifyDataSetChanged() + (binding?.downloadChildList?.adapter as? DownloadAdapter)?.submitList(eps) } } @@ -104,26 +100,15 @@ class DownloadChildFragment : Fragment() { setAppBarNoScrollFlagsOnTV() } - val adapter: RecyclerView.Adapter = - DownloadAdapter( - ArrayList(), - {} - ) { downloadClickEvent -> + val adapter = DownloadAdapter( + {}, + { downloadClickEvent -> handleDownloadClick(downloadClickEvent) if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) { - downloadDeleteEventListener = { id: Int -> - val list = - (binding?.downloadChildList?.adapter as DownloadAdapter?)?.cardList - if (list != null) { - if (list.any { it.data.id == id }) { - updateList(folder) - } - } - } - - downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it } + setUpDownloadDeleteListener(folder) } } + ) binding?.downloadChildList?.apply { setHasFixedSize(true) @@ -132,10 +117,22 @@ class DownloadChildFragment : Fragment() { setLinearListLayout( isHorizontal = false, nextRight = FOCUS_SELF, - nextDown = FOCUS_SELF + nextDown = FOCUS_SELF, ) } updateList(folder) } + + private fun setUpDownloadDeleteListener(folder: String) { + downloadDeleteEventListener = { id: Int -> + val list = (binding?.downloadChildList?.adapter as? DownloadAdapter)?.currentList + if (list != null) { + if (list.any { it.data.id == id }) { + updateList(folder) + } + } + } + downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it } + } } \ No newline at end of file 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 f7993570..1e2aad57 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 @@ -1,6 +1,5 @@ package com.lagradost.cloudstream3.ui.download -import android.annotation.SuppressLint import android.app.Dialog import android.content.ClipboardManager import android.content.Context @@ -11,6 +10,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout +import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isGone @@ -18,7 +18,6 @@ import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding @@ -46,7 +45,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV import com.lagradost.cloudstream3.utils.VideoDownloadManager import java.net.URI - const val DOWNLOAD_NAVIGATE_TO = "downloadpage" class DownloadFragment : Fragment() { @@ -61,36 +59,32 @@ class DownloadFragment : Fragment() { this.layoutParams = param } - @SuppressLint("NotifyDataSetChanged") private fun setList(list: List) { main { - (binding?.downloadList?.adapter as DownloadAdapter?)?.cardList = list - binding?.downloadList?.adapter?.notifyDataSetChanged() + (binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(list) } } override fun onDestroyView() { - if (downloadDeleteEventListener != null) { - VideoDownloadManager.downloadDeleteEvent -= downloadDeleteEventListener!! - downloadDeleteEventListener = null + downloadDeleteEventListener?.let { + VideoDownloadManager.downloadDeleteEvent -= it } + downloadDeleteEventListener = null binding = null super.onDestroyView() } - var binding: FragmentDownloadsBinding? = null + private var binding: FragmentDownloadsBinding? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - downloadsViewModel = - ViewModelProvider(this)[DownloadViewModel::class.java] - + downloadsViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] val localBinding = FragmentDownloadsBinding.inflate(inflater, container, false) binding = localBinding - return localBinding.root//inflater.inflate(R.layout.fragment_downloads, container, false) + return localBinding.root } private var downloadDeleteEventListener: ((Int) -> Unit)? = null @@ -98,7 +92,6 @@ class DownloadFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) hideKeyboard() - binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV() observe(downloadsViewModel.noDownloadsText) { @@ -109,80 +102,27 @@ class DownloadFragment : Fragment() { binding?.downloadLoading?.isVisible = false } observe(downloadsViewModel.availableBytes) { - binding?.downloadFreeTxt?.text = - getString(R.string.storage_size_format).format( - getString(R.string.free_storage), - formatShortFileSize(view.context, it) - ) - binding?.downloadFree?.setLayoutWidth(it) + updateStorageInfo(view.context, it, binding?.downloadFreeTxt, binding?.downloadFree) } observe(downloadsViewModel.usedBytes) { - binding?.apply { - downloadUsedTxt.text = - getString(R.string.storage_size_format).format( - getString(R.string.used_storage), - formatShortFileSize(view.context, it) - ) - downloadUsed.setLayoutWidth(it) - downloadStorageAppbar.isVisible = it > 0 - } + updateStorageInfo(view.context, it, binding?.downloadUsedTxt, binding?.downloadUsed) + binding?.downloadStorageAppbar?.isVisible = it > 0 } observe(downloadsViewModel.downloadBytes) { - binding?.apply { - downloadAppTxt.text = - getString(R.string.storage_size_format).format( - getString(R.string.app_storage), - formatShortFileSize(view.context, it) - ) - downloadApp.setLayoutWidth(it) - } + updateStorageInfo(view.context, it, binding?.downloadAppTxt, binding?.downloadApp) } - val adapter: RecyclerView.Adapter = - DownloadAdapter( - ArrayList(), - { click -> - when (click.action) { - 0 -> { - if (click.data.type.isMovieType()) { - // Won't be called - } else { - val folder = DataStore.getFolderName( - DOWNLOAD_EPISODE_CACHE, - click.data.id.toString() - ) - activity?.navigate( - R.id.action_navigation_downloads_to_navigation_download_child, - DownloadChildFragment.newInstance(click.data.name, folder) - ) - } - } - - 1 -> { - (activity as AppCompatActivity?)?.loadResult( - click.data.url, - click.data.apiName - ) - } - } - }, - { downloadClickEvent -> - handleDownloadClick(downloadClickEvent) - if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) { - downloadDeleteEventListener = { id -> - val list = (binding?.downloadList?.adapter as DownloadAdapter?)?.cardList - if (list != null) { - if (list.any { it.data.id == id }) { - context?.let { ctx -> - downloadsViewModel.updateList(ctx) - } - } - } - } - downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it } - } + val adapter = DownloadAdapter( + { click -> + handleItemClick(click) + }, + { downloadClickEvent -> + handleDownloadClick(downloadClickEvent) + if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) { + setUpDownloadDeleteListener() } - ) + } + ) binding?.downloadList?.apply { setHasFixedSize(true) @@ -192,86 +132,112 @@ class DownloadFragment : Fragment() { isHorizontal = false, nextRight = FOCUS_SELF, nextUp = FOCUS_SELF, - nextDown = FOCUS_SELF + nextDown = FOCUS_SELF, ) } - // Should be visible in emulator layout - binding?.downloadStreamButton?.isGone = isLayout(TV) - binding?.downloadStreamButton?.setOnClickListener { - val dialog = - Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom) - - val binding = StreamInputBinding.inflate(dialog.layoutInflater) - - dialog.setContentView(binding.root) - - dialog.show() - - // If user has clicked the switch do not interfere - var preventAutoSwitching = false - binding.hlsSwitch.setOnClickListener { - preventAutoSwitching = true - } - - fun activateSwitchOnHls(text: String?) { - binding.hlsSwitch.isChecked = normalSafeApiCall { - URI(text).path?.substringAfterLast(".")?.contains("m3u") - } == true - } - - binding.streamReferer.doOnTextChanged { text, _, _, _ -> - if (!preventAutoSwitching) - activateSwitchOnHls(text?.toString()) - } - - (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt( - 0 - )?.text?.toString()?.let { copy -> - val fixedText = copy.trim() - binding.streamUrl.setText(fixedText) - activateSwitchOnHls(fixedText) - } - - binding.applyBtt.setOnClickListener { - val url = binding.streamUrl.text?.toString() - if (url.isNullOrEmpty()) { - showToast(R.string.error_invalid_url, Toast.LENGTH_SHORT) - } else { - val referer = binding.streamReferer.text?.toString() - - activity?.navigate( - R.id.global_to_navigation_player, - GeneratorPlayer.newInstance( - LinkGenerator( - listOf(BasicLink(url)), - extract = true, - referer = referer, - isM3u8 = binding.hlsSwitch.isChecked - ) - ) - ) - - dialog.dismissSafe(activity) - } - } - - binding.cancelBtt.setOnClickListener { - dialog.dismissSafe(activity) - } + binding?.downloadStreamButton?.apply { + isGone = isLayout(TV) + setOnClickListener { showStreamInputDialog(it.context) } } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { binding?.downloadList?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> - val dy = scrollY - oldScrollY - if (dy > 0) { //check for scroll down - binding?.downloadStreamButton?.shrink() // hide - } else if (dy < -5) { - binding?.downloadStreamButton?.extend() // show - } + handleScroll(scrollY - oldScrollY) } } downloadsViewModel.updateList(requireContext()) - fixPaddingStatusbar(binding?.downloadRoot) } + + private fun handleItemClick(click: DownloadHeaderClickEvent) { + when (click.action) { + 0 -> { + if (!click.data.type.isMovieType()) { + val folder = DataStore.getFolderName(DOWNLOAD_EPISODE_CACHE, click.data.id.toString()) + activity?.navigate( + R.id.action_navigation_downloads_to_navigation_download_child, + DownloadChildFragment.newInstance(click.data.name, folder) + ) + } + } + 1 -> { + (activity as AppCompatActivity?)?.loadResult(click.data.url, click.data.apiName) + } + } + } + + private fun setUpDownloadDeleteListener() { + downloadDeleteEventListener = { id -> + val list = (binding?.downloadList?.adapter as? DownloadAdapter)?.currentList + if (list?.any { it.data.id == id } == true) { + context?.let { downloadsViewModel.updateList(it) } + } + } + downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it } + } + + private fun updateStorageInfo(context: Context, bytes: Long, textView: TextView?, view: View?) { + textView?.text = getString(R.string.storage_size_format).format(getString(R.string.free_storage), formatShortFileSize(context, bytes)) + view?.setLayoutWidth(bytes) + } + + private fun showStreamInputDialog(context: Context) { + val dialog = Dialog(context, R.style.AlertDialogCustom) + val binding = StreamInputBinding.inflate(dialog.layoutInflater) + dialog.setContentView(binding.root) + dialog.show() + + var preventAutoSwitching = false + binding.hlsSwitch.setOnClickListener { preventAutoSwitching = true } + + binding.streamReferer.doOnTextChanged { text, _, _, _ -> + if (!preventAutoSwitching) activateSwitchOnHls(text?.toString(), binding) + } + + (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager)?.primaryClip?.getItemAt(0)?.text?.toString()?.let { copy -> + val fixedText = copy.trim() + binding.streamUrl.setText(fixedText) + activateSwitchOnHls(fixedText, binding) + } + + binding.applyBtt.setOnClickListener { + val url = binding.streamUrl.text?.toString() + if (url.isNullOrEmpty()) { + showToast(R.string.error_invalid_url, Toast.LENGTH_SHORT) + } else { + val referer = binding.streamReferer.text?.toString() + activity?.navigate( + R.id.global_to_navigation_player, + GeneratorPlayer.newInstance( + LinkGenerator( + listOf(BasicLink(url)), + extract = true, + referer = referer, + isM3u8 = binding.hlsSwitch.isChecked + ) + ) + ) + dialog.dismissSafe(activity) + } + } + + binding.cancelBtt.setOnClickListener { + dialog.dismissSafe(activity) + } + } + + private fun activateSwitchOnHls(text: String?, binding: StreamInputBinding) { + binding.hlsSwitch.isChecked = normalSafeApiCall { + URI(text).path?.substringAfterLast(".")?.contains("m3u") + } == true + } + + private fun handleScroll(dy: Int) { + if (dy > 0) { + binding?.downloadStreamButton?.shrink() + } else if (dy < -5) { + binding?.downloadStreamButton?.extend() + } + } } \ No newline at end of file