Simplification of VisualDownloadCached handling and fix a bug

This commit is contained in:
Luna712 2024-07-08 17:44:54 -06:00 committed by GitHub
parent 595588aeca
commit 5ed9eee407
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 64 additions and 99 deletions

View file

@ -32,46 +32,26 @@ const val DOWNLOAD_ACTION_LONG_CLICK = 5
const val DOWNLOAD_ACTION_GO_TO_CHILD = 0 const val DOWNLOAD_ACTION_GO_TO_CHILD = 0
const val DOWNLOAD_ACTION_LOAD_RESULT = 1 const val DOWNLOAD_ACTION_LOAD_RESULT = 1
abstract class VisualDownloadCached( sealed class VisualDownloadCached {
open val currentBytes: Long, abstract val currentBytes: Long
open val totalBytes: Long, abstract val totalBytes: Long
open val data: VideoDownloadHelper.DownloadCached abstract val data: VideoDownloadHelper.DownloadCached
) {
// Just to be extra-safe with areContentsTheSame data class Child(
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
}
override fun hashCode(): Int {
var result = currentBytes.hashCode()
result = 31 * result + totalBytes.hashCode()
result = 31 * result + data.hashCode()
return result
}
}
data class VisualDownloadChildCached(
override val currentBytes: Long, override val currentBytes: Long,
override val totalBytes: Long, override val totalBytes: Long,
override val data: VideoDownloadHelper.DownloadEpisodeCached, override val data: VideoDownloadHelper.DownloadEpisodeCached,
): VisualDownloadCached(currentBytes, totalBytes, data) ) : VisualDownloadCached()
data class VisualDownloadHeaderCached( data class Header(
override val currentBytes: Long, override val currentBytes: Long,
override val totalBytes: Long, override val totalBytes: Long,
override val data: VideoDownloadHelper.DownloadHeaderCached, override val data: VideoDownloadHelper.DownloadHeaderCached,
val child: VideoDownloadHelper.DownloadEpisodeCached?, val child: VideoDownloadHelper.DownloadEpisodeCached?,
val currentOngoingDownloads: Int, val currentOngoingDownloads: Int,
val totalDownloads: Int, val totalDownloads: Int
): VisualDownloadCached(currentBytes, totalBytes, data) ) : VisualDownloadCached()
}
data class DownloadClickEvent( data class DownloadClickEvent(
val action: Int, val action: Int,
@ -83,16 +63,11 @@ data class DownloadHeaderClickEvent(
val data: VideoDownloadHelper.DownloadHeaderCached val data: VideoDownloadHelper.DownloadHeaderCached
) )
sealed class VisualDownloadItem {
data class Header(val header: VisualDownloadHeaderCached) : VisualDownloadItem()
data class Child(val child: VisualDownloadChildCached) : VisualDownloadItem()
}
class DownloadAdapter( class DownloadAdapter(
private val headerClickCallback: (DownloadHeaderClickEvent) -> Unit, private val headerClickCallback: (DownloadHeaderClickEvent) -> Unit,
private val mediaClickCallback: (DownloadClickEvent) -> Unit, private val mediaClickCallback: (DownloadClickEvent) -> Unit,
private val selectedChangedCallback: (VisualDownloadItem, Boolean) -> Unit, private val selectedChangedCallback: (VisualDownloadCached, Boolean) -> Unit,
private val multiDeleteStateCallback: (VisualDownloadItem) -> Unit, private val multiDeleteStateCallback: (VisualDownloadCached) -> Unit,
) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) { ) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) {
private var isMultiDeleteState: Boolean = false private var isMultiDeleteState: Boolean = false
@ -109,12 +84,12 @@ class DownloadAdapter(
fun bind(card: VisualDownloadCached?) { fun bind(card: VisualDownloadCached?) {
when (binding) { when (binding) {
is DownloadHeaderEpisodeBinding -> bindHeader(card as? VisualDownloadHeaderCached) is DownloadHeaderEpisodeBinding -> bindHeader(card as? VisualDownloadCached.Header)
is DownloadChildEpisodeBinding -> bindChild(card as? VisualDownloadChildCached) is DownloadChildEpisodeBinding -> bindChild(card as? VisualDownloadCached.Child)
} }
} }
private fun bindHeader(card: VisualDownloadHeaderCached?) { private fun bindHeader(card: VisualDownloadCached.Header?) {
if (binding !is DownloadHeaderEpisodeBinding || card == null) return if (binding !is DownloadHeaderEpisodeBinding || card == null) return
val data = card.data val data = card.data
@ -123,7 +98,7 @@ class DownloadAdapter(
setImage(data.poster) setImage(data.poster)
if (isMultiDeleteState) { if (isMultiDeleteState) {
setOnClickListener { setOnClickListener {
toggleIsChecked(deleteCheckbox, VisualDownloadItem.Header(card)) toggleIsChecked(deleteCheckbox, card)
} }
} else { } else {
setOnClickListener { setOnClickListener {
@ -132,7 +107,7 @@ class DownloadAdapter(
} }
setOnLongClickListener { setOnLongClickListener {
multiDeleteStateCallback.invoke(VisualDownloadItem.Header(card)) multiDeleteStateCallback.invoke(card)
true true
} }
} }
@ -146,7 +121,7 @@ class DownloadAdapter(
if (isMultiDeleteState) { if (isMultiDeleteState) {
deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> deleteCheckbox.setOnCheckedChangeListener { _, isChecked ->
selectedIds[data.id] = isChecked selectedIds[data.id] = isChecked
selectedChangedCallback.invoke(VisualDownloadItem.Header(card), isChecked) selectedChangedCallback.invoke(card, isChecked)
} }
} else deleteCheckbox.setOnCheckedChangeListener(null) } else deleteCheckbox.setOnCheckedChangeListener(null)
@ -158,7 +133,7 @@ class DownloadAdapter(
} }
private fun DownloadHeaderEpisodeBinding.handleChildDownload( private fun DownloadHeaderEpisodeBinding.handleChildDownload(
card: VisualDownloadHeaderCached, card: VisualDownloadCached.Header,
formattedSize: String formattedSize: String
) { ) {
card.child ?: return card.child ?: return
@ -188,14 +163,14 @@ class DownloadAdapter(
downloadButton.isVisible = !isMultiDeleteState downloadButton.isVisible = !isMultiDeleteState
downloadButton.setOnLongClickListener { downloadButton.setOnLongClickListener {
multiDeleteStateCallback.invoke(VisualDownloadItem.Header(card)) multiDeleteStateCallback.invoke(card)
true true
} }
episodeHolder.apply { episodeHolder.apply {
if (isMultiDeleteState) { if (isMultiDeleteState) {
setOnClickListener { setOnClickListener {
toggleIsChecked(deleteCheckbox, VisualDownloadItem.Header(card)) toggleIsChecked(deleteCheckbox, card)
} }
} else { } else {
setOnClickListener { setOnClickListener {
@ -204,7 +179,7 @@ class DownloadAdapter(
} }
setOnLongClickListener { setOnLongClickListener {
multiDeleteStateCallback.invoke(VisualDownloadItem.Header(card)) multiDeleteStateCallback.invoke(card)
true true
} }
} }
@ -212,7 +187,7 @@ class DownloadAdapter(
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun DownloadHeaderEpisodeBinding.handleParentDownload( private fun DownloadHeaderEpisodeBinding.handleParentDownload(
card: VisualDownloadHeaderCached, card: VisualDownloadCached.Header,
formattedSize: String formattedSize: String
) { ) {
downloadButton.isVisible = false downloadButton.isVisible = false
@ -232,7 +207,7 @@ class DownloadAdapter(
episodeHolder.apply { episodeHolder.apply {
if (isMultiDeleteState) { if (isMultiDeleteState) {
setOnClickListener { setOnClickListener {
toggleIsChecked(deleteCheckbox, VisualDownloadItem.Header(card)) toggleIsChecked(deleteCheckbox, card)
} }
} else { } else {
setOnClickListener { setOnClickListener {
@ -241,13 +216,13 @@ class DownloadAdapter(
} }
setOnLongClickListener { setOnLongClickListener {
multiDeleteStateCallback.invoke(VisualDownloadItem.Header(card)) multiDeleteStateCallback.invoke(card)
true true
} }
} }
} }
private fun bindChild(card: VisualDownloadChildCached?) { private fun bindChild(card: VisualDownloadCached.Child?) {
if (binding !is DownloadChildEpisodeBinding || card == null) return if (binding !is DownloadChildEpisodeBinding || card == null) return
val data = card.data val data = card.data
@ -286,7 +261,7 @@ class DownloadAdapter(
downloadButton.isVisible = !isMultiDeleteState downloadButton.isVisible = !isMultiDeleteState
downloadButton.setOnLongClickListener { downloadButton.setOnLongClickListener {
multiDeleteStateCallback.invoke(VisualDownloadItem.Child(card)) multiDeleteStateCallback.invoke(card)
true true
} }
@ -302,7 +277,7 @@ class DownloadAdapter(
downloadChildEpisodeHolder.apply { downloadChildEpisodeHolder.apply {
if (isMultiDeleteState) { if (isMultiDeleteState) {
setOnClickListener { setOnClickListener {
toggleIsChecked(deleteCheckbox, VisualDownloadItem.Child(card)) toggleIsChecked(deleteCheckbox, card)
} }
} else { } else {
setOnClickListener { setOnClickListener {
@ -311,7 +286,7 @@ class DownloadAdapter(
} }
setOnLongClickListener { setOnLongClickListener {
multiDeleteStateCallback.invoke(VisualDownloadItem.Child(card)) multiDeleteStateCallback.invoke(card)
true true
} }
} }
@ -319,7 +294,7 @@ class DownloadAdapter(
if (isMultiDeleteState) { if (isMultiDeleteState) {
deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> deleteCheckbox.setOnCheckedChangeListener { _, isChecked ->
selectedIds[data.id] = isChecked selectedIds[data.id] = isChecked
selectedChangedCallback.invoke(VisualDownloadItem.Child(card), isChecked) selectedChangedCallback.invoke(card, isChecked)
} }
} else deleteCheckbox.setOnCheckedChangeListener(null) } else deleteCheckbox.setOnCheckedChangeListener(null)
@ -347,8 +322,8 @@ class DownloadAdapter(
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when (getItem(position)) { return when (getItem(position)) {
is VisualDownloadChildCached -> VIEW_TYPE_CHILD is VisualDownloadCached.Child -> VIEW_TYPE_CHILD
is VisualDownloadHeaderCached -> VIEW_TYPE_HEADER is VisualDownloadCached.Header -> VIEW_TYPE_HEADER
else -> throw IllegalArgumentException("Invalid data type at position $position") else -> throw IllegalArgumentException("Invalid data type at position $position")
} }
} }
@ -383,13 +358,10 @@ class DownloadAdapter(
} }
} }
private fun toggleIsChecked(checkbox: CheckBox, item: VisualDownloadItem) { private fun toggleIsChecked(checkbox: CheckBox, item: VisualDownloadCached) {
val isChecked = !checkbox.isChecked val isChecked = !checkbox.isChecked
checkbox.isChecked = isChecked checkbox.isChecked = isChecked
when (item) { selectedIds[item.data.id] = isChecked
is VisualDownloadItem.Header -> selectedIds[item.header.data.id] = isChecked
is VisualDownloadItem.Child -> selectedIds[item.child.data.id] = isChecked
}
selectedChangedCallback.invoke(item, isChecked) selectedChangedCallback.invoke(item, isChecked)
} }

View file

@ -67,7 +67,7 @@ class DownloadChildFragment : Fragment() {
}.mapNotNull { }.mapNotNull {
val info = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(ctx, it.id) val info = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(ctx, it.id)
?: return@mapNotNull null ?: return@mapNotNull null
VisualDownloadChildCached( VisualDownloadCached.Child(
currentBytes = info.fileLength, currentBytes = info.fileLength,
totalBytes = info.totalBytes, totalBytes = info.totalBytes,
data = it, data = it,
@ -131,10 +131,9 @@ class DownloadChildFragment : Fragment() {
} else downloadsViewModel.removeSelected(card) } else downloadsViewModel.removeSelected(card)
}, },
{ card -> { card ->
if (card !is VisualDownloadItem.Child) return@DownloadAdapter
downloadsViewModel.addSelected(card) downloadsViewModel.addSelected(card)
(binding?.downloadChildList?.adapter as? DownloadAdapter)?.updateSelectedItem( (binding?.downloadChildList?.adapter as? DownloadAdapter)?.updateSelectedItem(
card.child.data.id, card.data.id,
true true
) )
} }
@ -154,7 +153,7 @@ class DownloadChildFragment : Fragment() {
updateList(folder) updateList(folder)
} }
private fun handleSelectedChange(selected: MutableList<VisualDownloadItem>) { private fun handleSelectedChange(selected: MutableList<VisualDownloadCached>) {
val adapter = binding?.downloadChildList?.adapter as? DownloadAdapter val adapter = binding?.downloadChildList?.adapter as? DownloadAdapter
if (selected.isNotEmpty()) { if (selected.isNotEmpty()) {
binding?.downloadDeleteAppbar?.isVisible = true binding?.downloadDeleteAppbar?.isVisible = true

View file

@ -130,10 +130,9 @@ class DownloadFragment : Fragment() {
} else downloadsViewModel.removeSelected(card) } else downloadsViewModel.removeSelected(card)
}, },
{ card -> { card ->
if (card !is VisualDownloadItem.Header) return@DownloadAdapter
downloadsViewModel.addSelected(card) downloadsViewModel.addSelected(card)
(binding?.downloadList?.adapter as? DownloadAdapter)?.updateSelectedItem( (binding?.downloadList?.adapter as? DownloadAdapter)?.updateSelectedItem(
card.header.data.id, card.data.id,
true true
) )
} }
@ -188,7 +187,7 @@ class DownloadFragment : Fragment() {
} }
} }
private fun handleSelectedChange(selected: MutableList<VisualDownloadItem>) { private fun handleSelectedChange(selected: MutableList<VisualDownloadCached>) {
val adapter = binding?.downloadList?.adapter as? DownloadAdapter val adapter = binding?.downloadList?.adapter as? DownloadAdapter
if (selected.isNotEmpty()) { if (selected.isNotEmpty()) {
binding?.downloadDeleteAppbar?.isVisible = true binding?.downloadDeleteAppbar?.isVisible = true
@ -211,10 +210,10 @@ class DownloadFragment : Fragment() {
adapter?.setIsMultiDeleteState(true) adapter?.setIsMultiDeleteState(true)
} else { } else {
binding?.downloadDeleteAppbar?.isVisible = false binding?.downloadDeleteAppbar?.isVisible = false
binding?.downloadStorageAppbar?.isVisible =
// Make sure we don't display it early // Make sure we don't display it early
!downloadsViewModel.headerCards.value.isNullOrEmpty() && if (downloadsViewModel.usedBytes.value?.let { it > 0 } == true) {
downloadsViewModel.usedBytes.value?.let { it > 0 } == true binding?.downloadStorageAppbar?.isVisible = true
}
adapter?.setIsMultiDeleteState(false) adapter?.setIsMultiDeleteState(false)
downloadsViewModel.clearSelectedItems() downloadsViewModel.clearSelectedItems()

View file

@ -27,24 +27,24 @@ import kotlinx.coroutines.withContext
class DownloadViewModel : ViewModel() { class DownloadViewModel : ViewModel() {
private val _headerCards = private val _headerCards =
MutableLiveData<List<VisualDownloadHeaderCached>>().apply { listOf<VisualDownloadHeaderCached>() } MutableLiveData<List<VisualDownloadCached.Header>>().apply { listOf<VisualDownloadCached.Header>() }
val headerCards: LiveData<List<VisualDownloadHeaderCached>> = _headerCards val headerCards: LiveData<List<VisualDownloadCached.Header>> = _headerCards
private val _usedBytes = MutableLiveData<Long>() private val _usedBytes = MutableLiveData<Long>()
private val _availableBytes = MutableLiveData<Long>() private val _availableBytes = MutableLiveData<Long>()
private val _downloadBytes = MutableLiveData<Long>() private val _downloadBytes = MutableLiveData<Long>()
private val _selectedItems = MutableLiveData<MutableList<VisualDownloadItem>>(mutableListOf()) private val _selectedItems = MutableLiveData<MutableList<VisualDownloadCached>>(mutableListOf())
val usedBytes: LiveData<Long> = _usedBytes val usedBytes: LiveData<Long> = _usedBytes
val availableBytes: LiveData<Long> = _availableBytes val availableBytes: LiveData<Long> = _availableBytes
val downloadBytes: LiveData<Long> = _downloadBytes val downloadBytes: LiveData<Long> = _downloadBytes
val selectedItems: LiveData<MutableList<VisualDownloadItem>> = _selectedItems val selectedItems: LiveData<MutableList<VisualDownloadCached>> = _selectedItems
private var previousVisual: List<VisualDownloadHeaderCached>? = null private var previousVisual: List<VisualDownloadCached.Header>? = null
fun addSelected(item: VisualDownloadItem) { fun addSelected(item: VisualDownloadCached) {
val currentSelected = selectedItems.value ?: mutableListOf() val currentSelected = selectedItems.value ?: mutableListOf()
if (!currentSelected.contains(item)) { if (!currentSelected.contains(item)) {
currentSelected.add(item) currentSelected.add(item)
@ -52,7 +52,7 @@ class DownloadViewModel : ViewModel() {
} }
} }
fun removeSelected(item: VisualDownloadItem) { fun removeSelected(item: VisualDownloadCached) {
selectedItems.value?.let { selected -> selectedItems.value?.let { selected ->
selected.remove(item) selected.remove(item)
_selectedItems.postValue(selected) _selectedItems.postValue(selected)
@ -63,8 +63,8 @@ class DownloadViewModel : ViewModel() {
val currentSelected = selectedItems.value ?: mutableListOf() val currentSelected = selectedItems.value ?: mutableListOf()
val items = headerCards.value ?: return val items = headerCards.value ?: return
items.forEach { item -> items.forEach { item ->
if (!currentSelected.contains(VisualDownloadItem.Header(item))) { if (!currentSelected.contains(item)) {
currentSelected.add(VisualDownloadItem.Header(item)) currentSelected.add(item)
} }
} }
_selectedItems.postValue(currentSelected) _selectedItems.postValue(currentSelected)
@ -124,7 +124,7 @@ class DownloadViewModel : ViewModel() {
DOWNLOAD_EPISODE_CACHE, DOWNLOAD_EPISODE_CACHE,
getFolderName(it.id.toString(), it.id.toString()) getFolderName(it.id.toString(), it.id.toString())
) )
VisualDownloadHeaderCached( VisualDownloadCached.Header(
currentBytes = currentBytes, currentBytes = currentBytes,
totalBytes = bytes, totalBytes = bytes,
data = it, data = it,
@ -145,7 +145,7 @@ class DownloadViewModel : ViewModel() {
} }
} }
private fun updateStorageStats(visual: List<VisualDownloadHeaderCached>) { private fun updateStorageStats(visual: List<VisualDownloadCached.Header>) {
try { try {
val stat = StatFs(Environment.getExternalStorageDirectory().path) val stat = StatFs(Environment.getExternalStorageDirectory().path)
val localBytesAvailable = stat.availableBytes val localBytesAvailable = stat.availableBytes
@ -164,17 +164,12 @@ class DownloadViewModel : ViewModel() {
fun handleMultiDelete(context: Context) = viewModelScope.launchSafe { fun handleMultiDelete(context: Context) = viewModelScope.launchSafe {
val selectedItemsList = selectedItems.value ?: mutableListOf() val selectedItemsList = selectedItems.value ?: mutableListOf()
val ids = selectedItemsList.map { val ids = selectedItemsList.map { it.data.id }
when (it) {
is VisualDownloadItem.Header -> it.header.data.id
is VisualDownloadItem.Child -> it.child.data.id
}
}
val names = selectedItemsList.mapNotNull { val names = selectedItemsList.mapNotNull {
when (it) { when (it) {
is VisualDownloadItem.Header -> it.header.data.name is VisualDownloadCached.Header -> it.data.name
is VisualDownloadItem.Child -> it.child.data.name is VisualDownloadCached.Child -> it.data.name
} }
} }