Faster remove downloads from adapter after deletion

This commit is contained in:
Luna712 2024-07-05 19:36:43 +00:00
parent 145c42f1c8
commit 3e62382d3e
4 changed files with 92 additions and 49 deletions

View file

@ -251,6 +251,15 @@ class DownloadAdapter(
} }
} }
fun removeItem(itemId: Int) {
val currentList = currentList.toMutableList()
val position = currentList.indexOfFirst { it.data.id == itemId }
if (position != -1) {
currentList.removeAt(position)
notifyItemRemoved(position)
}
}
class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() { class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {
override fun areItemsTheSame(oldItem: VisualDownloadCached, newItem: VisualDownloadCached): Boolean { override fun areItemsTheSame(oldItem: VisualDownloadCached, newItem: VisualDownloadCached): Boolean {
return oldItem.data.id == newItem.data.id return oldItem.data.id == newItem.data.id

View file

@ -127,11 +127,11 @@ class DownloadChildFragment : Fragment() {
private fun setUpDownloadDeleteListener(folder: String) { private fun setUpDownloadDeleteListener(folder: String) {
downloadDeleteEventListener = { id: Int -> downloadDeleteEventListener = { id: Int ->
val list = (binding?.downloadChildList?.adapter as? DownloadAdapter)?.currentList val adapter = binding?.downloadChildList?.adapter as? DownloadAdapter
if (list != null) { val list = adapter?.currentList
if (list.any { it.data.id == id }) { if (list?.any { it.data.id == id }) {
updateList(folder) adapter.removeItem(id)
} updateList(folder)
} }
} }
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it } downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }

View file

@ -170,9 +170,11 @@ class DownloadFragment : Fragment() {
private fun setUpDownloadDeleteListener() { private fun setUpDownloadDeleteListener() {
downloadDeleteEventListener = { id -> downloadDeleteEventListener = { id ->
val list = (binding?.downloadList?.adapter as? DownloadAdapter)?.currentList val adapter = binding?.downloadList?.adapter as? DownloadAdapter
val list = adapter?.currentList
if (list?.any { it.data.id == id } == true) { if (list?.any { it.data.id == id } == true) {
context?.let { downloadsViewModel.updateList(it) } adapter.removeItem(id)
downloadsViewModel.removeItem(id)
} }
} }
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it } downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }

View file

@ -35,74 +35,96 @@ class DownloadViewModel : ViewModel() {
private var previousVisual: List<VisualDownloadHeaderCached>? = null private var previousVisual: List<VisualDownloadHeaderCached>? = null
fun updateList(context: Context) = viewModelScope.launchSafe { fun updateList(context: Context) {
val children = withContext(Dispatchers.IO) { viewModelScope.launchSafe {
context.getKeys(DOWNLOAD_EPISODE_CACHE) try {
.mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) } val children = withContext(Dispatchers.IO) {
.distinctBy { it.id } // Remove duplicates fetchDownloadedEpisodes(context)
} }
val visual = withContext(Dispatchers.IO) {
generateVisualList(context, children)
}
// parentId : bytes if (visual != previousVisual) {
previousVisual = visual
updateBytes(context, visual)
_headerCards.postValue(visual)
}
} catch (e: Exception) {
logError(e)
}
}
}
private fun fetchDownloadedEpisodes(context: Context): List<VideoDownloadHelper.DownloadEpisodeCached> {
return context.getKeys(DOWNLOAD_EPISODE_CACHE)
.mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) }
.distinctBy { it.id }
}
private suspend fun generateVisualList(
context: Context,
children: List<VideoDownloadHelper.DownloadEpisodeCached>
): List<VisualDownloadHeaderCached> {
val totalBytesUsedByChild = HashMap<Int, Long>() val totalBytesUsedByChild = HashMap<Int, Long>()
// parentId : bytes
val currentBytesUsedByChild = HashMap<Int, Long>() val currentBytesUsedByChild = HashMap<Int, Long>()
// parentId : downloadsCount
val totalDownloads = HashMap<Int, Int>() val totalDownloads = HashMap<Int, Int>()
// Gets all children downloads
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
children.forEach { c -> children.forEach { c ->
val childFile = getDownloadFileInfoAndUpdateSettings(context, c.id) ?: return@forEach val childFile =
getDownloadFileInfoAndUpdateSettings(context, c.id) ?: return@forEach
if (childFile.fileLength <= 1) return@forEach if (childFile.fileLength <= 1) return@forEach
val len = childFile.totalBytes val len = childFile.totalBytes
val flen = childFile.fileLength val flen = childFile.fileLength
totalBytesUsedByChild[c.parentId] = totalBytesUsedByChild[c.parentId]?.plus(len) ?: len totalBytesUsedByChild[c.parentId] =
currentBytesUsedByChild[c.parentId] = currentBytesUsedByChild[c.parentId]?.plus(flen) ?: flen totalBytesUsedByChild[c.parentId]?.plus(len) ?: len
currentBytesUsedByChild[c.parentId] =
currentBytesUsedByChild[c.parentId]?.plus(flen) ?: flen
totalDownloads[c.parentId] = totalDownloads[c.parentId]?.plus(1) ?: 1 totalDownloads[c.parentId] = totalDownloads[c.parentId]?.plus(1) ?: 1
} }
} }
val cached = withContext(Dispatchers.IO) { // Won't fetch useless keys return totalDownloads.entries
totalDownloads.entries.filter { it.value > 0 }.mapNotNull { .filter { it.value > 0 }
.mapNotNull {
context.getKey<VideoDownloadHelper.DownloadHeaderCached>( context.getKey<VideoDownloadHelper.DownloadHeaderCached>(
DOWNLOAD_HEADER_CACHE, DOWNLOAD_HEADER_CACHE,
it.key.toString() it.key.toString()
) )
} }
} .mapNotNull {
val visual = withContext(Dispatchers.IO) {
cached.mapNotNull {
val downloads = totalDownloads[it.id] ?: 0 val downloads = totalDownloads[it.id] ?: 0
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
val movieEpisode = if (bytes <= 0 || downloads <= 0) null
if (!it.type.isMovieType()) null else {
val movieEpisode = if (!it.type.isMovieType()) null
else context.getKey<VideoDownloadHelper.DownloadEpisodeCached>( else context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(
DOWNLOAD_EPISODE_CACHE, DOWNLOAD_EPISODE_CACHE,
getFolderName(it.id.toString(), it.id.toString()) getFolderName(it.id.toString(), it.id.toString())
) )
VisualDownloadHeaderCached(
currentBytes = currentBytes,
totalBytes = bytes,
data = it,
child = movieEpisode,
currentOngoingDownloads = 0,
totalDownloads = downloads,
)
}.sortedBy {
(it.child?.episode ?: 0) + (it.child?.season?.times(10000) ?: 0)
} // Episode sorting by episode, lowest to highest
}
// Only update list if different from the previous one to prevent duplicate initialization VisualDownloadHeaderCached(
if (visual != previousVisual) { currentBytes = currentBytes,
previousVisual = visual totalBytes = bytes,
data = it,
child = movieEpisode,
currentOngoingDownloads = 0,
totalDownloads = downloads,
)
}
}
.sortedBy { (it.child?.episode ?: 0) + (it.child?.season?.times(10000) ?: 0) }
}
try { private suspend fun updateBytes(context: Context, visual: List<VisualDownloadHeaderCached>) {
try {
withContext(Dispatchers.IO) {
val stat = StatFs(Environment.getExternalStorageDirectory().path) val stat = StatFs(Environment.getExternalStorageDirectory().path)
val localBytesAvailable = stat.availableBytes val localBytesAvailable = stat.availableBytes
val localTotalBytes = stat.blockSizeLong * stat.blockCountLong val localTotalBytes = stat.blockSizeLong * stat.blockCountLong
@ -111,12 +133,22 @@ class DownloadViewModel : ViewModel() {
_usedBytes.postValue(localTotalBytes - localBytesAvailable - localDownloadedBytes) _usedBytes.postValue(localTotalBytes - localBytesAvailable - localDownloadedBytes)
_availableBytes.postValue(localBytesAvailable) _availableBytes.postValue(localBytesAvailable)
_downloadBytes.postValue(localDownloadedBytes) _downloadBytes.postValue(localDownloadedBytes)
} catch (t: Throwable) {
_downloadBytes.postValue(0)
logError(t)
} }
} catch (t: Throwable) {
_downloadBytes.postValue(0)
logError(t)
}
}
_headerCards.postValue(visual) fun removeItem(itemId: Int) {
val visual = _headerCards.value?.toMutableList() ?: mutableListOf()
val position = visual.indexOfFirst { it.data.id == itemId }
if (position != -1) {
visual.removeAt(position)
if (visual != previousVisual) {
previousVisual = visual
_headerCards.postValue(visual)
}
} }
} }
} }