Only use IDs for storing in view model

This commit is contained in:
Luna712 2024-07-17 10:00:41 -06:00 committed by GitHub
parent fb78676a81
commit 821f6e3186
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 84 additions and 69 deletions

View file

@ -68,7 +68,7 @@ data class DownloadHeaderClickEvent(
class DownloadAdapter( class DownloadAdapter(
private val onHeaderClickEvent: (DownloadHeaderClickEvent) -> Unit, private val onHeaderClickEvent: (DownloadHeaderClickEvent) -> Unit,
private val onItemClickEvent: (DownloadClickEvent) -> Unit, private val onItemClickEvent: (DownloadClickEvent) -> Unit,
private val onItemSelectionChanged: (VisualDownloadCached, Boolean) -> Unit, private val onItemSelectionChanged: (Int, Boolean) -> Unit,
) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) { ) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) {
private var isMultiDeleteState: Boolean = false private var isMultiDeleteState: Boolean = false
@ -97,12 +97,12 @@ class DownloadAdapter(
episodeHolder.apply { episodeHolder.apply {
if (isMultiDeleteState) { if (isMultiDeleteState) {
setOnClickListener { setOnClickListener {
toggleIsChecked(deleteCheckbox, card) toggleIsChecked(deleteCheckbox, data.id)
} }
} }
setOnLongClickListener { setOnLongClickListener {
toggleIsChecked(deleteCheckbox, card) toggleIsChecked(deleteCheckbox, data.id)
true true
} }
} }
@ -111,7 +111,7 @@ class DownloadAdapter(
setImage(data.poster) setImage(data.poster)
if (isMultiDeleteState) { if (isMultiDeleteState) {
setOnClickListener { setOnClickListener {
toggleIsChecked(deleteCheckbox, card) toggleIsChecked(deleteCheckbox, data.id)
} }
} else { } else {
setOnClickListener { setOnClickListener {
@ -125,7 +125,7 @@ class DownloadAdapter(
} }
setOnLongClickListener { setOnLongClickListener {
toggleIsChecked(deleteCheckbox, card) toggleIsChecked(deleteCheckbox, data.id)
true true
} }
} }
@ -138,7 +138,7 @@ class DownloadAdapter(
if (isMultiDeleteState) { if (isMultiDeleteState) {
deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> deleteCheckbox.setOnCheckedChangeListener { _, isChecked ->
onItemSelectionChanged.invoke(card, isChecked) onItemSelectionChanged.invoke(data.id, isChecked)
} }
} else deleteCheckbox.setOnCheckedChangeListener(null) } else deleteCheckbox.setOnCheckedChangeListener(null)
@ -287,7 +287,7 @@ class DownloadAdapter(
when { when {
isMultiDeleteState -> { isMultiDeleteState -> {
setOnClickListener { setOnClickListener {
toggleIsChecked(deleteCheckbox, card) toggleIsChecked(deleteCheckbox, data.id)
} }
} }
@ -304,14 +304,14 @@ class DownloadAdapter(
} }
setOnLongClickListener { setOnLongClickListener {
toggleIsChecked(deleteCheckbox, card) toggleIsChecked(deleteCheckbox, data.id)
true true
} }
} }
if (isMultiDeleteState) { if (isMultiDeleteState) {
deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> deleteCheckbox.setOnCheckedChangeListener { _, isChecked ->
onItemSelectionChanged.invoke(card, isChecked) onItemSelectionChanged.invoke(data.id, isChecked)
} }
} else deleteCheckbox.setOnCheckedChangeListener(null) } else deleteCheckbox.setOnCheckedChangeListener(null)
@ -371,10 +371,10 @@ class DownloadAdapter(
} }
} }
private fun toggleIsChecked(checkbox: CheckBox, item: VisualDownloadCached) { private fun toggleIsChecked(checkbox: CheckBox, id: Int) {
val isChecked = !checkbox.isChecked val isChecked = !checkbox.isChecked
checkbox.isChecked = isChecked checkbox.isChecked = isChecked
onItemSelectionChanged.invoke(item, isChecked) onItemSelectionChanged.invoke(id, isChecked)
} }
class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() { class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {

View file

@ -118,9 +118,9 @@ class DownloadChildFragment : Fragment() {
} }
} }
observe(downloadsViewModel.selectedBytes) { observe(downloadsViewModel.selectedBytes) {
updateDeleteButton(downloadsViewModel.selectedItems.value?.count() ?: 0, it) updateDeleteButton(downloadsViewModel.selectedItemIds.value?.count() ?: 0, it)
} }
observe(downloadsViewModel.selectedItems) { observe(downloadsViewModel.selectedItemIds) {
handleSelectedChange(it) handleSelectedChange(it)
updateDeleteButton(it.count(), downloadsViewModel.selectedBytes.value ?: 0L) updateDeleteButton(it.count(), downloadsViewModel.selectedBytes.value ?: 0L)
@ -141,10 +141,10 @@ class DownloadChildFragment : Fragment() {
setUpDownloadDeleteListener(folder) setUpDownloadDeleteListener(folder)
} }
}, },
{ card, isChecked -> { itemId, isChecked ->
if (isChecked) { if (isChecked) {
downloadsViewModel.addSelected(card) downloadsViewModel.addSelected(itemId)
} else downloadsViewModel.removeSelected(card) } else downloadsViewModel.removeSelected(itemId)
} }
) )
@ -162,7 +162,7 @@ class DownloadChildFragment : Fragment() {
downloadsViewModel.updateChildList(requireContext(), folder) downloadsViewModel.updateChildList(requireContext(), folder)
} }
private fun handleSelectedChange(selected: MutableList<VisualDownloadCached>) { private fun handleSelectedChange(selected: MutableSet<Int>) {
if (selected.isNotEmpty()) { if (selected.isNotEmpty()) {
binding?.downloadDeleteAppbar?.isVisible = true binding?.downloadDeleteAppbar?.isVisible = true
binding?.downloadChildToolbar?.isVisible = false binding?.downloadChildToolbar?.isVisible = false

View file

@ -151,7 +151,7 @@ class DownloadFragment : Fragment() {
) )
} }
observe(downloadsViewModel.selectedBytes) { observe(downloadsViewModel.selectedBytes) {
updateDeleteButton(downloadsViewModel.selectedItems.value?.count() ?: 0, it) updateDeleteButton(downloadsViewModel.selectedItemIds.value?.count() ?: 0, it)
} }
observe(downloadsViewModel.isMultiDeleteState) { isMultiDeleteState -> observe(downloadsViewModel.isMultiDeleteState) { isMultiDeleteState ->
val adapter = binding?.downloadList?.adapter as? DownloadAdapter val adapter = binding?.downloadList?.adapter as? DownloadAdapter
@ -167,7 +167,7 @@ class DownloadFragment : Fragment() {
} }
} }
} }
observe(downloadsViewModel.selectedItems) { observe(downloadsViewModel.selectedItemIds) {
handleSelectedChange(it) handleSelectedChange(it)
updateDeleteButton(it.count(), downloadsViewModel.selectedBytes.value ?: 0L) updateDeleteButton(it.count(), downloadsViewModel.selectedBytes.value ?: 0L)
@ -188,10 +188,10 @@ class DownloadFragment : Fragment() {
setUpDownloadDeleteListener() setUpDownloadDeleteListener()
} }
}, },
{ card, isChecked -> { itemId, isChecked ->
if (isChecked) { if (isChecked) {
downloadsViewModel.addSelected(card) downloadsViewModel.addSelected(itemId)
} else downloadsViewModel.removeSelected(card) } else downloadsViewModel.removeSelected(itemId)
} }
) )
@ -245,8 +245,8 @@ class DownloadFragment : Fragment() {
} }
} }
private fun handleSelectedChange(selectedItems: MutableList<VisualDownloadCached>) { private fun handleSelectedChange(selected: MutableSet<Int>) {
if (selectedItems.isNotEmpty()) { if (selected.isNotEmpty()) {
binding?.downloadDeleteAppbar?.isVisible = true binding?.downloadDeleteAppbar?.isVisible = true
binding?.downloadStorageAppbar?.isVisible = false binding?.downloadStorageAppbar?.isVisible = false
activity?.attachBackPressedCallback { activity?.attachBackPressedCallback {

View file

@ -52,8 +52,8 @@ class DownloadViewModel : ViewModel() {
private val _isMultiDeleteState = MutableLiveData(false) private val _isMultiDeleteState = MutableLiveData(false)
val isMultiDeleteState: LiveData<Boolean> = _isMultiDeleteState val isMultiDeleteState: LiveData<Boolean> = _isMultiDeleteState
private val _selectedItems = MutableLiveData<MutableList<VisualDownloadCached>>(mutableListOf()) private val _selectedItemIds = MutableLiveData<MutableSet<Int>>(mutableSetOf())
val selectedItems: LiveData<MutableList<VisualDownloadCached>> = _selectedItems val selectedItemIds: LiveData<MutableSet<Int>> = _selectedItemIds
private var previousVisual: List<VisualDownloadCached>? = null private var previousVisual: List<VisualDownloadCached>? = null
@ -61,35 +61,35 @@ class DownloadViewModel : ViewModel() {
_isMultiDeleteState.postValue(value) _isMultiDeleteState.postValue(value)
} }
fun addSelected(item: VisualDownloadCached) { fun addSelected(itemId: Int) {
val currentSelected = selectedItems.value ?: mutableListOf() val currentSelected = selectedItemIds.value ?: mutableSetOf()
if (!currentSelected.contains(item)) { if (!currentSelected.contains(itemId)) {
currentSelected.add(item) currentSelected.add(itemId)
_selectedItems.postValue(currentSelected) _selectedItemIds.postValue(currentSelected)
updateSelectedBytes() updateSelectedBytes()
updateSelectedCards() updateSelectedCards()
} }
} }
fun removeSelected(item: VisualDownloadCached) { fun removeSelected(itemId: Int) {
selectedItems.value?.let { selected -> selectedItemIds.value?.let { selected ->
selected.remove(item) selected.remove(itemId)
_selectedItems.postValue(selected) _selectedItemIds.postValue(selected)
updateSelectedBytes() updateSelectedBytes()
updateSelectedCards() updateSelectedCards()
} }
} }
fun selectAllItems() { fun selectAllItems() {
val currentSelected = selectedItems.value ?: mutableListOf() val currentSelected = selectedItemIds.value ?: mutableSetOf()
val items = (headerCards.value ?: emptyList()) + (childCards.value ?: emptyList()) val items = (headerCards.value ?: emptyList()) + (childCards.value ?: emptyList())
if (items.isEmpty()) return if (items.isEmpty()) return
items.forEach { item -> items.forEach { item ->
if (!currentSelected.contains(item)) { if (!currentSelected.contains(item.data.id)) {
currentSelected.add(item) currentSelected.add(item.data.id)
} }
} }
_selectedItems.postValue(currentSelected) _selectedItemIds.postValue(currentSelected)
updateSelectedBytes() updateSelectedBytes()
updateSelectedCards() updateSelectedCards()
} }
@ -97,31 +97,31 @@ class DownloadViewModel : ViewModel() {
fun clearSelectedItems() { fun clearSelectedItems() {
// We need this to be done immediately // We need this to be done immediately
// so we can't use postValue // so we can't use postValue
_selectedItems.value = mutableListOf() _selectedItemIds.value = mutableSetOf()
updateSelectedCards() updateSelectedCards()
} }
fun isAllSelected(): Boolean { fun isAllSelected(): Boolean {
val currentSelected = selectedItems.value ?: return false val currentSelected = selectedItemIds.value ?: return false
val headerItems = headerCards.value val headerItems = headerCards.value
val childItems = childCards.value val childItems = childCards.value
if (headerItems != null && if (headerItems != null &&
headerItems.count() == currentSelected.count() && headerItems.count() == currentSelected.count() &&
headerItems.containsAll(currentSelected) headerItems.map { it.data.id }.containsAll(currentSelected)
) return true ) return true
if (childItems != null && if (childItems != null &&
childItems.count() == currentSelected.count() && childItems.count() == currentSelected.count() &&
childItems.containsAll(currentSelected) childItems.map { it.data.id }.containsAll(currentSelected)
) return true ) return true
return false return false
} }
private fun updateSelectedBytes() = viewModelScope.launchSafe { private fun updateSelectedBytes() = viewModelScope.launchSafe {
val selectedItemsList = selectedItems.value ?: return@launchSafe val selectedItemsList = getSelectedItemsData() ?: return@launchSafe
var totalSelectedBytes = 0L var totalSelectedBytes = 0L
selectedItemsList.forEach { item -> selectedItemsList.forEach { item ->
@ -132,20 +132,16 @@ class DownloadViewModel : ViewModel() {
} }
private fun updateSelectedCards() = viewModelScope.launchSafe { private fun updateSelectedCards() = viewModelScope.launchSafe {
val currentSelected = selectedItems.value ?: return@launchSafe val currentSelected = selectedItemIds.value ?: return@launchSafe
val updatedHeaderCards = headerCards.value?.toMutableList() val updatedHeaderCards = headerCards.value?.toMutableList()
val updatedChildCards = childCards.value?.toMutableList() val updatedChildCards = childCards.value?.toMutableList()
updatedHeaderCards?.forEach { header -> updatedHeaderCards?.forEach { header ->
header.isSelected = currentSelected.any { header.isSelected = currentSelected.contains(header.data.id)
it.data.id == header.data.id
}
} }
updatedChildCards?.forEach { child -> updatedChildCards?.forEach { child ->
child.isSelected = currentSelected.any { child.isSelected = currentSelected.contains(child.data.id)
it.data.id == child.data.id
}
} }
_headerCards.postValue(updatedHeaderCards) _headerCards.postValue(updatedHeaderCards)
@ -199,9 +195,7 @@ class DownloadViewModel : ViewModel() {
val bytes = totalBytesUsedByChild[it.id] ?: 0 val bytes = totalBytesUsedByChild[it.id] ?: 0
val currentBytes = currentBytesUsedByChild[it.id] ?: 0 val currentBytes = currentBytesUsedByChild[it.id] ?: 0
if (bytes <= 0 || downloads <= 0) return@mapNotNull null if (bytes <= 0 || downloads <= 0) return@mapNotNull null
val isSelected = selectedItems.value?.any { header -> val isSelected = selectedItemIds.value?.contains(it.id) ?: false
it.id == header.data.id
} ?: false
val movieEpisode = val movieEpisode =
if (!it.type.isMovieType()) null if (!it.type.isMovieType()) null
else context.getKey<VideoDownloadHelper.DownloadEpisodeCached>( else context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(
@ -239,11 +233,9 @@ class DownloadViewModel : ViewModel() {
data.mapNotNull { key -> data.mapNotNull { key ->
context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(key) context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(key)
}.mapNotNull { }.mapNotNull {
val isSelected = selectedItemIds.value?.contains(it.id) ?: false
val info = getDownloadFileInfoAndUpdateSettings(context, it.id) val info = getDownloadFileInfoAndUpdateSettings(context, it.id)
?: return@mapNotNull null ?: return@mapNotNull null
val isSelected = selectedItems.value?.any { child ->
it.id == child.data.id
} ?: false
VisualDownloadCached.Child( VisualDownloadCached.Child(
currentBytes = info.fileLength, currentBytes = info.fileLength,
totalBytes = info.totalBytes, totalBytes = info.totalBytes,
@ -279,8 +271,26 @@ class DownloadViewModel : ViewModel() {
context: Context, context: Context,
onDeleteConfirm: () -> Unit onDeleteConfirm: () -> Unit
) = viewModelScope.launchSafe { ) = viewModelScope.launchSafe {
val selectedItemsList = selectedItems.value ?: emptyList() val selectedItemsList = getSelectedItemsData() ?: emptyList()
val deleteData = processSelectedItems(context, selectedItemsList)
val message = buildDeleteMessage(context, deleteData)
showDeleteConfirmationDialog(context, message, deleteData.ids, onDeleteConfirm)
}
private fun getSelectedItemsData(): List<VisualDownloadCached>? {
val selectedIds = selectedItemIds.value ?: return null
val headers = headerCards.value ?: emptyList()
val children = childCards.value ?: emptyList()
return (headers + children).filter { item ->
selectedIds.contains(item.data.id)
}
}
private fun processSelectedItems(
context: Context,
selectedItemsList: List<VisualDownloadCached>
): DeleteData {
val ids = mutableListOf<Int>() val ids = mutableListOf<Int>()
val seriesNames = mutableListOf<String>() val seriesNames = mutableListOf<String>()
val names = mutableListOf<String>() val names = mutableListOf<String>()
@ -331,20 +341,17 @@ class DownloadViewModel : ViewModel() {
} }
} }
val data = DeleteConfirmationData(parentName, seriesNames.toList(), names.toList()) return DeleteData(ids, seriesNames, names, parentName)
showDeleteConfirmationDialog(context, ids, data, onDeleteConfirm)
} }
private fun showDeleteConfirmationDialog( private fun buildDeleteMessage(
context: Context, context: Context,
ids: List<Int>, data: DeleteData
data: DeleteConfirmationData, ): String {
onDeleteConfirm: () -> Unit
) {
val formattedNames = data.names.joinToString(separator = "\n") { "$it" } val formattedNames = data.names.joinToString(separator = "\n") { "$it" }
val formattedSeriesNames = data.seriesNames.joinToString(separator = "\n") { "$it" } val formattedSeriesNames = data.seriesNames.joinToString(separator = "\n") { "$it" }
val message = when { return when {
data.seriesNames.isNotEmpty() && data.names.isEmpty() -> { data.seriesNames.isNotEmpty() && data.names.isEmpty() -> {
context.getString(R.string.delete_message_series_only).format(formattedSeriesNames) context.getString(R.string.delete_message_series_only).format(formattedSeriesNames)
} }
@ -363,7 +370,14 @@ class DownloadViewModel : ViewModel() {
else -> context.getString(R.string.delete_message_multiple).format(formattedNames) else -> context.getString(R.string.delete_message_multiple).format(formattedNames)
} }
}
private fun showDeleteConfirmationDialog(
context: Context,
message: String,
ids: List<Int>,
onDeleteConfirm: () -> Unit
) {
val builder = AlertDialog.Builder(context) val builder = AlertDialog.Builder(context)
val dialogClickListener = val dialogClickListener =
DialogInterface.OnClickListener { _, which -> DialogInterface.OnClickListener { _, which ->
@ -393,9 +407,10 @@ class DownloadViewModel : ViewModel() {
} }
} }
private data class DeleteConfirmationData( private data class DeleteData(
val parentName: String?, val ids: List<Int>,
val seriesNames: List<String>, val seriesNames: List<String>,
val names: List<String> val names: List<String>,
val parentName: String?
) )
} }