Rewrite to use DiffUtil and massive cleanup

This commit is contained in:
Luna712 2024-06-20 18:33:22 -06:00 committed by GitHub
parent ea0531f82a
commit 2122677663
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 189 additions and 215 deletions

View file

@ -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<VisualDownloadCached>,
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.Adapter<DownloadAdapter.DownloadViewHolder>() {
) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) {
companion object {
private const val VIEW_TYPE_HEADER = 0
@ -161,9 +174,8 @@ class DownloadAdapter(
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder =
DownloadViewHolder(
binding = when (viewType) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder {
val binding = when (viewType) {
VIEW_TYPE_HEADER -> {
DownloadHeaderEpisodeBinding.inflate(
LayoutInflater.from(parent.context),
@ -179,27 +191,26 @@ class DownloadAdapter(
)
}
else -> throw IllegalArgumentException("Invalid view type")
},
clickCallback,
mediaClickCallback
)
}
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<VisualDownloadCached>() {
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
}
}
}

View file

@ -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.DownloadViewHolder> =
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 }
}
}

View file

@ -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<VisualDownloadHeaderCached>) {
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,77 +102,24 @@ 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.DownloadViewHolder> =
DownloadAdapter(
ArrayList(),
val adapter = DownloadAdapter(
{ 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
)
}
}
handleItemClick(click)
},
{ 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 }
setUpDownloadDeleteListener()
}
}
)
@ -192,45 +132,73 @@ 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)
binding?.downloadStreamButton?.apply {
isGone = isLayout(TV)
setOnClickListener { showStreamInputDialog(it.context) }
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
binding?.downloadList?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
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()
// 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.hlsSwitch.setOnClickListener { preventAutoSwitching = true }
binding.streamReferer.doOnTextChanged { text, _, _, _ ->
if (!preventAutoSwitching)
activateSwitchOnHls(text?.toString())
if (!preventAutoSwitching) activateSwitchOnHls(text?.toString(), binding)
}
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt(
0
)?.text?.toString()?.let { copy ->
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager)?.primaryClip?.getItemAt(0)?.text?.toString()?.let { copy ->
val fixedText = copy.trim()
binding.streamUrl.setText(fixedText)
activateSwitchOnHls(fixedText)
activateSwitchOnHls(fixedText, binding)
}
binding.applyBtt.setOnClickListener {
@ -239,7 +207,6 @@ class DownloadFragment : Fragment() {
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(
@ -251,7 +218,6 @@ class DownloadFragment : Fragment() {
)
)
)
dialog.dismissSafe(activity)
}
}
@ -260,18 +226,18 @@ class DownloadFragment : Fragment() {
dialog.dismissSafe(activity)
}
}
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
}
}
}
downloadsViewModel.updateList(requireContext())
fixPaddingStatusbar(binding?.downloadRoot)
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()
}
}
}