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.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.lagradost.cloudstream3.R 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.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
const val DOWNLOAD_ACTION_PLAY_FILE = 0 const val DOWNLOAD_ACTION_PLAY_FILE = 0
const val DOWNLOAD_ACTION_DELETE_FILE = 1 const val DOWNLOAD_ACTION_DELETE_FILE = 1
const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2 const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2
@ -29,7 +30,20 @@ abstract class VisualDownloadCached(
open val currentBytes: Long, open val currentBytes: Long,
open val totalBytes: Long, open val totalBytes: Long,
open val data: VideoDownloadHelper.DownloadCached 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( data class VisualDownloadChildCached(
override val currentBytes: Long, override val currentBytes: Long,
@ -57,10 +71,9 @@ data class DownloadHeaderClickEvent(
) )
class DownloadAdapter( class DownloadAdapter(
var cardList: List<VisualDownloadCached>,
private val clickCallback: (DownloadHeaderClickEvent) -> Unit, private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
private val mediaClickCallback: (DownloadClickEvent) -> Unit, private val mediaClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.Adapter<DownloadAdapter.DownloadViewHolder>() { ) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) {
companion object { companion object {
private const val VIEW_TYPE_HEADER = 0 private const val VIEW_TYPE_HEADER = 0
@ -161,9 +174,8 @@ class DownloadAdapter(
} }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder {
DownloadViewHolder( val binding = when (viewType) {
binding = when (viewType) {
VIEW_TYPE_HEADER -> { VIEW_TYPE_HEADER -> {
DownloadHeaderEpisodeBinding.inflate( DownloadHeaderEpisodeBinding.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
@ -179,27 +191,26 @@ class DownloadAdapter(
) )
} }
else -> throw IllegalArgumentException("Invalid view type") else -> throw IllegalArgumentException("Invalid view type")
}, }
clickCallback, return DownloadViewHolder(binding, clickCallback, mediaClickCallback)
mediaClickCallback }
)
override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) { 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 { override fun getItemViewType(position: Int): Int {
if (viewType != 0) return viewType val card = getItem(position)
return if (card is VisualDownloadChildCached) VIEW_TYPE_CHILD else VIEW_TYPE_HEADER
val card = cardList.getOrNull(position) ?: return 0
val isChildView = card is VisualDownloadChildCached
return if (isChildView) VIEW_TYPE_CHILD else VIEW_TYPE_HEADER
} }
override fun getItemCount(): Int { class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {
return cardList.count() 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 package com.lagradost.cloudstream3.ui.download
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater 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.RecyclerView
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
@ -41,7 +39,8 @@ class DownloadChildFragment : Fragment() {
super.onDestroyView() super.onDestroyView()
} }
var binding: FragmentChildDownloadsBinding? = null private var binding: FragmentChildDownloadsBinding? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -49,10 +48,9 @@ class DownloadChildFragment : Fragment() {
): View { ): View {
val localBinding = FragmentChildDownloadsBinding.inflate(inflater, container, false) val localBinding = FragmentChildDownloadsBinding.inflate(inflater, container, false)
binding = localBinding 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 { private fun updateList(folder: String) = main {
context?.let { ctx -> context?.let { ctx ->
val data = withContext(Dispatchers.IO) { ctx.getKeys(folder) } val data = withContext(Dispatchers.IO) { ctx.getKeys(folder) }
@ -74,9 +72,7 @@ class DownloadChildFragment : Fragment() {
return@main return@main
} }
(binding?.downloadChildList?.adapter as DownloadAdapter? ?: return@main).cardList = (binding?.downloadChildList?.adapter as? DownloadAdapter)?.submitList(eps)
eps
binding?.downloadChildList?.adapter?.notifyDataSetChanged()
} }
} }
@ -104,26 +100,15 @@ class DownloadChildFragment : Fragment() {
setAppBarNoScrollFlagsOnTV() setAppBarNoScrollFlagsOnTV()
} }
val adapter: RecyclerView.Adapter<DownloadAdapter.DownloadViewHolder> = val adapter = DownloadAdapter(
DownloadAdapter( {},
ArrayList(), { downloadClickEvent ->
{}
) { downloadClickEvent ->
handleDownloadClick(downloadClickEvent) handleDownloadClick(downloadClickEvent)
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) { if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
downloadDeleteEventListener = { id: Int -> setUpDownloadDeleteListener(folder)
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 }
} }
} }
)
binding?.downloadChildList?.apply { binding?.downloadChildList?.apply {
setHasFixedSize(true) setHasFixedSize(true)
@ -132,10 +117,22 @@ class DownloadChildFragment : Fragment() {
setLinearListLayout( setLinearListLayout(
isHorizontal = false, isHorizontal = false,
nextRight = FOCUS_SELF, nextRight = FOCUS_SELF,
nextDown = FOCUS_SELF nextDown = FOCUS_SELF,
) )
} }
updateList(folder) 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 package com.lagradost.cloudstream3.ui.download
import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
@ -11,6 +10,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone import androidx.core.view.isGone
@ -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.RecyclerView
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding
@ -46,7 +45,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.setAppBarNoScrollFlagsOnTV
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
import java.net.URI import java.net.URI
const val DOWNLOAD_NAVIGATE_TO = "downloadpage" const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
class DownloadFragment : Fragment() { class DownloadFragment : Fragment() {
@ -61,36 +59,32 @@ class DownloadFragment : Fragment() {
this.layoutParams = param this.layoutParams = param
} }
@SuppressLint("NotifyDataSetChanged")
private fun setList(list: List<VisualDownloadHeaderCached>) { private fun setList(list: List<VisualDownloadHeaderCached>) {
main { main {
(binding?.downloadList?.adapter as DownloadAdapter?)?.cardList = list (binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(list)
binding?.downloadList?.adapter?.notifyDataSetChanged()
} }
} }
override fun onDestroyView() { override fun onDestroyView() {
if (downloadDeleteEventListener != null) { downloadDeleteEventListener?.let {
VideoDownloadManager.downloadDeleteEvent -= downloadDeleteEventListener!! VideoDownloadManager.downloadDeleteEvent -= it
downloadDeleteEventListener = null
} }
downloadDeleteEventListener = null
binding = null binding = null
super.onDestroyView() super.onDestroyView()
} }
var binding: FragmentDownloadsBinding? = null private var binding: FragmentDownloadsBinding? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
downloadsViewModel = downloadsViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
ViewModelProvider(this)[DownloadViewModel::class.java]
val localBinding = FragmentDownloadsBinding.inflate(inflater, container, false) val localBinding = FragmentDownloadsBinding.inflate(inflater, container, false)
binding = localBinding binding = localBinding
return localBinding.root//inflater.inflate(R.layout.fragment_downloads, container, false) return localBinding.root
} }
private var downloadDeleteEventListener: ((Int) -> Unit)? = null private var downloadDeleteEventListener: ((Int) -> Unit)? = null
@ -98,7 +92,6 @@ class DownloadFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
hideKeyboard() hideKeyboard()
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV() binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
observe(downloadsViewModel.noDownloadsText) { observe(downloadsViewModel.noDownloadsText) {
@ -109,77 +102,24 @@ class DownloadFragment : Fragment() {
binding?.downloadLoading?.isVisible = false binding?.downloadLoading?.isVisible = false
} }
observe(downloadsViewModel.availableBytes) { observe(downloadsViewModel.availableBytes) {
binding?.downloadFreeTxt?.text = updateStorageInfo(view.context, it, binding?.downloadFreeTxt, binding?.downloadFree)
getString(R.string.storage_size_format).format(
getString(R.string.free_storage),
formatShortFileSize(view.context, it)
)
binding?.downloadFree?.setLayoutWidth(it)
} }
observe(downloadsViewModel.usedBytes) { observe(downloadsViewModel.usedBytes) {
binding?.apply { updateStorageInfo(view.context, it, binding?.downloadUsedTxt, binding?.downloadUsed)
downloadUsedTxt.text = binding?.downloadStorageAppbar?.isVisible = it > 0
getString(R.string.storage_size_format).format(
getString(R.string.used_storage),
formatShortFileSize(view.context, it)
)
downloadUsed.setLayoutWidth(it)
downloadStorageAppbar.isVisible = it > 0
}
} }
observe(downloadsViewModel.downloadBytes) { observe(downloadsViewModel.downloadBytes) {
binding?.apply { updateStorageInfo(view.context, it, binding?.downloadAppTxt, binding?.downloadApp)
downloadAppTxt.text =
getString(R.string.storage_size_format).format(
getString(R.string.app_storage),
formatShortFileSize(view.context, it)
)
downloadApp.setLayoutWidth(it)
}
} }
val adapter: RecyclerView.Adapter<DownloadAdapter.DownloadViewHolder> = val adapter = DownloadAdapter(
DownloadAdapter(
ArrayList(),
{ click -> { click ->
when (click.action) { handleItemClick(click)
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 -> { downloadClickEvent ->
handleDownloadClick(downloadClickEvent) handleDownloadClick(downloadClickEvent)
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) { if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
downloadDeleteEventListener = { id -> setUpDownloadDeleteListener()
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 }
} }
} }
) )
@ -192,45 +132,73 @@ class DownloadFragment : Fragment() {
isHorizontal = false, isHorizontal = false,
nextRight = FOCUS_SELF, nextRight = FOCUS_SELF,
nextUp = FOCUS_SELF, nextUp = FOCUS_SELF,
nextDown = FOCUS_SELF nextDown = FOCUS_SELF,
) )
} }
// Should be visible in emulator layout binding?.downloadStreamButton?.apply {
binding?.downloadStreamButton?.isGone = isLayout(TV) isGone = isLayout(TV)
binding?.downloadStreamButton?.setOnClickListener { setOnClickListener { showStreamInputDialog(it.context) }
val dialog = }
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
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) val binding = StreamInputBinding.inflate(dialog.layoutInflater)
dialog.setContentView(binding.root) dialog.setContentView(binding.root)
dialog.show() dialog.show()
// If user has clicked the switch do not interfere
var preventAutoSwitching = false var preventAutoSwitching = false
binding.hlsSwitch.setOnClickListener { binding.hlsSwitch.setOnClickListener { preventAutoSwitching = true }
preventAutoSwitching = true
}
fun activateSwitchOnHls(text: String?) {
binding.hlsSwitch.isChecked = normalSafeApiCall {
URI(text).path?.substringAfterLast(".")?.contains("m3u")
} == true
}
binding.streamReferer.doOnTextChanged { text, _, _, _ -> binding.streamReferer.doOnTextChanged { text, _, _, _ ->
if (!preventAutoSwitching) if (!preventAutoSwitching) activateSwitchOnHls(text?.toString(), binding)
activateSwitchOnHls(text?.toString())
} }
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt( (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager)?.primaryClip?.getItemAt(0)?.text?.toString()?.let { copy ->
0
)?.text?.toString()?.let { copy ->
val fixedText = copy.trim() val fixedText = copy.trim()
binding.streamUrl.setText(fixedText) binding.streamUrl.setText(fixedText)
activateSwitchOnHls(fixedText) activateSwitchOnHls(fixedText, binding)
} }
binding.applyBtt.setOnClickListener { binding.applyBtt.setOnClickListener {
@ -239,7 +207,6 @@ class DownloadFragment : Fragment() {
showToast(R.string.error_invalid_url, Toast.LENGTH_SHORT) showToast(R.string.error_invalid_url, Toast.LENGTH_SHORT)
} else { } else {
val referer = binding.streamReferer.text?.toString() val referer = binding.streamReferer.text?.toString()
activity?.navigate( activity?.navigate(
R.id.global_to_navigation_player, R.id.global_to_navigation_player,
GeneratorPlayer.newInstance( GeneratorPlayer.newInstance(
@ -251,7 +218,6 @@ class DownloadFragment : Fragment() {
) )
) )
) )
dialog.dismissSafe(activity) dialog.dismissSafe(activity)
} }
} }
@ -260,18 +226,18 @@ class DownloadFragment : Fragment() {
dialog.dismissSafe(activity) 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()
}
} }
} }