Use DownloadStatOperation to update screen

This commit is contained in:
semenvav 2023-11-04 15:30:19 +02:00
parent c00fe31a3f
commit 75fd51d328
11 changed files with 105 additions and 125 deletions

View file

@ -0,0 +1,26 @@
package eu.kanade.presentation.more.download
import eu.kanade.presentation.more.download.data.DownloadStatManga
fun getDownloadStatMangaSort(
sortingMode: SortingMode,
sortDescending: Boolean,
): (
DownloadStatManga,
DownloadStatManga,
) -> Int {
return when (sortingMode) {
SortingMode.BY_ALPHABET -> when (sortDescending) {
true -> { m1, m2 -> m1.libraryManga.manga.title.compareTo(m2.libraryManga.manga.title) }
false -> { m1, m2 -> m2.libraryManga.manga.title.compareTo(m1.libraryManga.manga.title) }
}
SortingMode.BY_SIZE -> when (sortDescending) {
true -> { m1, m2 -> m2.folderSize.compareTo(m1.folderSize) }
false -> { m1, m2 -> m1.folderSize.compareTo(m2.folderSize) }
}
SortingMode.BY_CHAPTERS -> when (sortDescending) {
true -> { m1, m2 -> m2.downloadChaptersCount.compareTo(m1.downloadChaptersCount) }
false -> { m1, m2 -> m1.downloadChaptersCount.compareTo(m2.downloadChaptersCount) }
}
}
}

View file

@ -86,8 +86,8 @@ class DownloadStatsScreen : Screen() {
sortMode = state.sortMode, sortMode = state.sortMode,
groupByMode = state.groupByMode, groupByMode = state.groupByMode,
showNotDownloaded = state.showNotDownloaded, showNotDownloaded = state.showNotDownloaded,
onSort = screenModel::runSort, onSort = screenModel::changeSortMode,
onGroup = screenModel::runGroupBy, onGroup = screenModel::changeGroupByMode,
toggleShowNotDownloaded = screenModel::toggleShowNoDownload, toggleShowNotDownloaded = screenModel::toggleShowNoDownload,
) )
} }

View file

@ -4,7 +4,7 @@ import android.content.Context
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
import eu.kanade.presentation.more.download.components.graphic.GraphGroupByMode import eu.kanade.presentation.more.download.components.graphic.GraphGroupByMode
import eu.kanade.presentation.more.download.components.graphic.GraphicPoint import eu.kanade.presentation.more.download.components.graphic.GraphicPoint
@ -14,6 +14,8 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.download.DownloadProvider
import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.lang.toRelativeString
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
@ -48,74 +50,82 @@ class DownloadStatsScreenModel(
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
) : StateScreenModel<DownloadStatsScreenState>(DownloadStatsScreenState()) { ) : StateScreenModel<DownloadStatsScreenState>(DownloadStatsScreenState()) {
var activeCategoryIndex: Int by preferenceStore.getInt("downloadStatSelectedTab", 0).asState(coroutineScope) var activeCategoryIndex: Int by preferenceStore.getInt("downloadStatSelectedTab", 0).asState(screenModelScope)
private lateinit var lastSelectedManga: LibraryManga private lateinit var lastSelectedManga: LibraryManga
init { init {
coroutineScope.launchIO { screenModelScope.launchIO {
val sortMode = preferenceStore.getEnum("sort_mode", SortingMode.BY_ALPHABET).get() val sortMode = preferenceStore.getEnum("sort_mode", SortingMode.BY_ALPHABET).get()
mutableState.update { state -> mutableState.update { state ->
val categories = getCategories.await().associateBy { group -> group.id } val categories = getCategories.await().associateBy { group -> group.id }
val operations = getDownloadStatOperations.await()
state.copy( state.copy(
items = getLibraryManga.await().filter { libraryManga -> items = getLibraryManga.await().map { libraryManga ->
(downloadManager.getDownloadCount(libraryManga.manga) > 0) || operations.any { it.mangaId == libraryManga.id }
}.mapNotNull { libraryManga ->
val source = sourceManager.getOrStub(libraryManga.manga.source) val source = sourceManager.getOrStub(libraryManga.manga.source)
val path = downloadProvider.findMangaDir( val path = downloadProvider.findMangaDir(
libraryManga.manga.title, libraryManga.manga.title,
source, source,
)?.filePath )?.filePath
val downloadChaptersCount = downloadManager.getDownloadCount(libraryManga.manga) DownloadStatManga(
if (downloadChaptersCount == 0) { libraryManga = libraryManga,
DownloadStatManga( source = source,
libraryManga = libraryManga, folderSize = if(path != null) DiskUtil.getDirectorySize(File(path)) else 0,
source = source, downloadChaptersCount = downloadManager.getDownloadCount(libraryManga.manga),
category = categories[libraryManga.category]!!, category = categories[libraryManga.category]!!,
) )
} else if (path != null) {
DownloadStatManga(
libraryManga = libraryManga,
source = source,
folderSize = DiskUtil.getDirectorySize(File(path)),
downloadChaptersCount = downloadChaptersCount,
category = categories[libraryManga.category]!!,
)
} else {
null
}
}, },
groupByMode = preferenceStore.getEnum("group_by_mode", GroupByMode.NONE).get(), groupByMode = preferenceStore.getEnum("group_by_mode", GroupByMode.NONE).get(),
sortMode = sortMode, sortMode = sortMode,
descendingOrder = preferenceStore.getBoolean("descending_order", false).get(), descendingOrder = preferenceStore.getBoolean("descending_order", false).get(),
searchQuery = preferenceStore.getString("search_query", "").get().takeIf { string -> string != "" }, searchQuery = preferenceStore.getString("search_query", "").get().takeIf { string -> string != "" },
downloadStatOperations = operations, downloadStatOperations = getDownloadStatOperations.await(),
showNotDownloaded = preferenceStore.getBoolean("show_no_downloaded", false).get(), showNotDownloaded = preferenceStore.getBoolean("show_no_downloaded", false).get(),
isLoading = false, isLoading = false,
) )
} }
runSort(sortMode, true) }
screenModelScope.launchIO {
getDownloadStatOperations.subscribe().distinctUntilChanged().collectLatest { operations ->
mutableState.update { state ->
val oldOperationsId = state.downloadStatOperations.map { it.id }.toHashSet()
val newOperations = operations.mapNotNull { if(!oldOperationsId.contains(it.id)) it else null }.groupBy { it.mangaId }
val newItems = state.items.map { item ->
if(newOperations.containsKey(item.libraryManga.id)){
item.copy(
folderSize = item.folderSize + newOperations[item.libraryManga.id]!!.sumOf { it.size },
downloadChaptersCount = item.downloadChaptersCount + newOperations[item.libraryManga.id]!!.sumOf { it.units }.toInt(),
)
} else item
}
state.copy(
items = newItems,
downloadStatOperations = operations,
)
}
}
} }
} }
fun runSort( fun changeSortMode(
mode: SortingMode, mode: SortingMode,
initSort: Boolean = false,
) { ) {
when (mode) { mutableState.update { state ->
SortingMode.BY_ALPHABET -> sortByAlphabet(initSort) val descendingOrder = if (state.sortMode == mode) !state.descendingOrder else false
SortingMode.BY_SIZE -> sortBySize(initSort) preferenceStore.getBoolean("descending_order", false).set(descendingOrder)
SortingMode.BY_CHAPTERS -> sortByChapters(initSort) state.copy(
descendingOrder = descendingOrder,
sortMode = mode,
)
} }
preferenceStore.getEnum("sort_mode", SortingMode.BY_ALPHABET).set(mode) preferenceStore.getEnum("sort_mode", SortingMode.BY_ALPHABET).set(mode)
} }
fun runGroupBy(mode: GroupByMode) { fun changeGroupByMode(mode: GroupByMode) {
when (mode) { mutableState.update {
GroupByMode.NONE -> unGroup() it.copy(
GroupByMode.BY_CATEGORY -> groupByCategory() groupByMode = mode,
GroupByMode.BY_SOURCE -> groupBySource() )
} }
preferenceStore.getEnum("group_by_mode", GroupByMode.NONE).set(mode) preferenceStore.getEnum("group_by_mode", GroupByMode.NONE).set(mode)
} }
@ -129,42 +139,6 @@ class DownloadStatsScreenModel(
} }
} }
private fun sortByAlphabet(initSort: Boolean) {
mutableState.update { state ->
val descendingOrder = if (initSort) state.descendingOrder else if (state.sortMode == SortingMode.BY_ALPHABET) !state.descendingOrder else false
preferenceStore.getBoolean("descending_order", false).set(descendingOrder)
state.copy(
items = if (descendingOrder) state.items.sortedByDescending { it.libraryManga.manga.title } else state.items.sortedBy { it.libraryManga.manga.title },
descendingOrder = descendingOrder,
sortMode = SortingMode.BY_ALPHABET,
)
}
}
private fun sortBySize(initSort: Boolean) {
mutableState.update { state ->
val descendingOrder = if (initSort) state.descendingOrder else if (state.sortMode == SortingMode.BY_SIZE) !state.descendingOrder else false
preferenceStore.getBoolean("descending_order", false).set(descendingOrder)
state.copy(
items = if (descendingOrder) state.items.sortedByDescending { it.folderSize } else state.items.sortedBy { it.folderSize },
descendingOrder = descendingOrder,
sortMode = SortingMode.BY_SIZE,
)
}
}
private fun sortByChapters(initSort: Boolean) {
mutableState.update { state ->
val descendingOrder = if (initSort) state.descendingOrder else if (state.sortMode == SortingMode.BY_CHAPTERS) !state.descendingOrder else false
preferenceStore.getBoolean("descending_order", false).set(descendingOrder)
state.copy(
items = if (descendingOrder) state.items.sortedByDescending { it.downloadChaptersCount } else state.items.sortedBy { it.downloadChaptersCount },
descendingOrder = descendingOrder,
sortMode = SortingMode.BY_CHAPTERS,
)
}
}
fun categoryMap( fun categoryMap(
items: List<DownloadStatManga>, items: List<DownloadStatManga>,
groupMode: GroupByMode, groupMode: GroupByMode,
@ -186,13 +160,13 @@ class DownloadStatsScreenModel(
sortedMap sortedMap
} }
SortingMode.BY_SIZE -> { SortingMode.BY_SIZE -> {
val compareFun: (String) -> Comparable<*> = { it: String -> unsortedMap[it]?.sumOf { manga -> manga.folderSize } ?: 0 } val compareFun: (String) -> Comparable<*> = { unsortedMap[it]?.sumOf { manga -> manga.folderSize } ?: 0 }
val sortedMap = TreeMap<String, List<DownloadStatManga>>(if (descendingOrder) { compareByDescending { compareFun(it) } } else { compareBy { compareFun(it) } }) val sortedMap = TreeMap<String, List<DownloadStatManga>>(if (descendingOrder) { compareByDescending { compareFun(it) } } else { compareBy { compareFun(it) } })
sortedMap.putAll(unsortedMap) sortedMap.putAll(unsortedMap)
sortedMap sortedMap
} }
SortingMode.BY_CHAPTERS -> { SortingMode.BY_CHAPTERS -> {
val compareFun: (String) -> Comparable<*> = { it: String -> unsortedMap[it]?.sumOf { manga -> manga.downloadChaptersCount } ?: 0 } val compareFun: (String) -> Comparable<*> = { unsortedMap[it]?.sumOf { manga -> manga.downloadChaptersCount } ?: 0 }
val sortedMap = TreeMap<String, List<DownloadStatManga>>(if (descendingOrder) { compareByDescending { compareFun(it) } } else { compareBy { compareFun(it) } }) val sortedMap = TreeMap<String, List<DownloadStatManga>>(if (descendingOrder) { compareByDescending { compareFun(it) } } else { compareBy { compareFun(it) } })
sortedMap.putAll(unsortedMap) sortedMap.putAll(unsortedMap)
sortedMap sortedMap
@ -200,30 +174,6 @@ class DownloadStatsScreenModel(
} }
} }
private fun groupBySource() {
mutableState.update {
it.copy(
groupByMode = GroupByMode.BY_SOURCE,
)
}
}
private fun groupByCategory() {
mutableState.update {
it.copy(
groupByMode = GroupByMode.BY_CATEGORY,
)
}
}
private fun unGroup() {
mutableState.update {
it.copy(
groupByMode = GroupByMode.NONE,
)
}
}
fun toggleSelection( fun toggleSelection(
item: DownloadStatManga, item: DownloadStatManga,
) { ) {
@ -246,17 +196,13 @@ class DownloadStatsScreenModel(
val processedItems = if (state.groupByMode == GroupByMode.NONE) { val processedItems = if (state.groupByMode == GroupByMode.NONE) {
state.processedItems(false) state.processedItems(false)
} else { } else {
val temp = mutableListOf<DownloadStatManga>()
categoryMap( categoryMap(
items = state.processedItems(false), items = state.processedItems(false),
groupMode = state.groupByMode, groupMode = state.groupByMode,
sortMode = state.sortMode, sortMode = state.sortMode,
descendingOrder = state.descendingOrder, descendingOrder = state.descendingOrder,
defaultCategoryName = null, defaultCategoryName = null,
).map { ).flatMap { it.value }
temp.addAll(it.value)
}
temp
} }
val lastSelectedIndex = val lastSelectedIndex =
processedItems.indexOfFirst { it.libraryManga.id == lastSelectedManga.id && it.libraryManga.category == lastSelectedManga.category } processedItems.indexOfFirst { it.libraryManga.id == lastSelectedManga.id && it.libraryManga.category == lastSelectedManga.category }
@ -344,18 +290,11 @@ class DownloadStatsScreenModel(
} }
fun deleteMangas(manga: List<DownloadStatManga>) { fun deleteMangas(manga: List<DownloadStatManga>) {
coroutineScope.launchNonCancellable { screenModelScope.launchNonCancellable {
manga.forEach { manga -> manga.forEach { manga ->
downloadManager.deleteManga(manga.libraryManga.manga, manga.source) downloadManager.deleteManga(manga.libraryManga.manga, manga.source)
} }
} }
val toDeleteIds = manga.map { it.libraryManga.manga.id }.toHashSet()
toggleAllSelection(false)
mutableState.update { state ->
state.copy(
items = state.items.map { if (it.libraryManga.manga.id in toDeleteIds) it.copy(downloadChaptersCount = 0, folderSize = 0) else it },
)
}
} }
fun showDeleteAlert(items: List<DownloadStatManga>) { fun showDeleteAlert(items: List<DownloadStatManga>) {
@ -495,6 +434,6 @@ sealed class Dialog {
data class DeleteManga(val items: List<DownloadStatManga>) : Dialog() data class DeleteManga(val items: List<DownloadStatManga>) : Dialog()
data class DownloadStatOperationInfo(val downloadStatOperation: DownloadStatOperation) : Dialog() data class DownloadStatOperationInfo(val downloadStatOperation: DownloadStatOperation) : Dialog()
data class MultiMangaDownloadStatOperationInfo(val downloadStatOperation: List<DownloadStatOperation>) : Dialog() data class MultiMangaDownloadStatOperationInfo(val downloadStatOperation: List<DownloadStatOperation>) : Dialog()
object DownloadStatOperationStart : Dialog() data object DownloadStatOperationStart : Dialog()
object SettingsSheet : Dialog() data object SettingsSheet : Dialog()
} }

View file

@ -1,6 +1,7 @@
package eu.kanade.presentation.more.download package eu.kanade.presentation.more.download
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.util.fastAny
import eu.kanade.presentation.more.download.data.DownloadStatManga import eu.kanade.presentation.more.download.data.DownloadStatManga
import tachiyomi.domain.stat.model.DownloadStatOperation import tachiyomi.domain.stat.model.DownloadStatOperation
@ -31,9 +32,9 @@ data class DownloadStatsScreenState(
} }
fun processedItems(unique: Boolean): List<DownloadStatManga> { fun processedItems(unique: Boolean): List<DownloadStatManga> {
return (if (unique) uniqueItems() else items).filter { return (if (unique) uniqueItems() else items)
if (!showNotDownloaded) it.downloadChaptersCount > 0 else true .filter { item -> item.downloadChaptersCount > 0 || if (showNotDownloaded) downloadStatOperations.fastAny { it.mangaId == item.libraryManga.id } else false }
}.filter { .filter {
if (searchQuery != null) { if (searchQuery != null) {
it.libraryManga.manga.title.contains(searchQuery, true) || it.libraryManga.manga.title.contains(searchQuery, true) ||
if (groupByMode == GroupByMode.BY_SOURCE) { it.source.name.contains(searchQuery, true) } else { false } || if (groupByMode == GroupByMode.BY_SOURCE) { it.source.name.contains(searchQuery, true) } else { false } ||
@ -42,5 +43,6 @@ data class DownloadStatsScreenState(
true true
} }
} }
.sortedWith { manga1, manga2 -> getDownloadStatMangaSort(sortMode, descendingOrder).invoke(manga1, manga2) }
} }
} }

View file

@ -199,7 +199,7 @@ private fun MangaInfoColumn(
Text( Text(
text = String.format( text = String.format(
stringResource(R.string.download_stat_operation_deleted), stringResource(R.string.download_stat_operation_deleted),
deleteItems.sumOf { it.units }, abs(deleteItems.sumOf { it.units }),
folderSizeText( folderSizeText(
folderSize = abs(deleteItems.sumOf { it.size }), folderSize = abs(deleteItems.sumOf { it.size }),
), ),

View file

@ -371,7 +371,7 @@ fun MangaDownloadStatSection(
TitleRow( TitleRow(
titles = listOf( titles = listOf(
stringResource(R.string.deleted_chapters), stringResource(R.string.deleted_chapters),
items.filter { it.size < 0 }.sumOf { it.units }.toString(), abs(items.filter { it.size < 0 }.sumOf { it.units }).toString(),
folderSizeText(items.filter { it.size < 0 }.sumOf { abs(it.size) }), folderSizeText(items.filter { it.size < 0 }.sumOf { abs(it.size) }),
), ),
titleStyle = MaterialTheme.typography.bodyMedium, titleStyle = MaterialTheme.typography.bodyMedium,

View file

@ -65,7 +65,7 @@ fun DeletedStatsRow(
StatsSection(R.string.deleted_chapters) { StatsSection(R.string.deleted_chapters) {
Row { Row {
StatsItem( StatsItem(
data.size.toString(), abs(data.sumOf { it.units }).toString(),
stringResource(R.string.label_total_chapters), stringResource(R.string.label_total_chapters),
) )
StatsItem( StatsItem(

View file

@ -232,7 +232,7 @@ class DownloadManager(
DownloadStatOperation.create().copy( DownloadStatOperation.create().copy(
mangaId = manga.id, mangaId = manga.id,
size = chapterDirs.sumOf { DiskUtil.getDirectorySize(File(it.filePath!!)) } * -1, size = chapterDirs.sumOf { DiskUtil.getDirectorySize(File(it.filePath!!)) } * -1,
units = filteredChapters.size.toLong(), units = filteredChapters.size.toLong() * -1,
), ),
) )
@ -265,7 +265,7 @@ class DownloadManager(
DownloadStatOperation.create().copy( DownloadStatOperation.create().copy(
mangaId = manga.id, mangaId = manga.id,
size = dirSize * -1, size = dirSize * -1,
units = cache.getDownloadCount(manga).toLong(), units = cache.getDownloadCount(manga).toLong() * -1,
), ),
) )
} }

View file

@ -1,5 +1,6 @@
package tachiyomi.data.stat package tachiyomi.data.stat
import kotlinx.coroutines.flow.Flow
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
import tachiyomi.domain.stat.model.DownloadStatOperation import tachiyomi.domain.stat.model.DownloadStatOperation
import tachiyomi.domain.stat.repository.DownloadStatRepository import tachiyomi.domain.stat.repository.DownloadStatRepository
@ -12,6 +13,10 @@ class DownloadStatRepositoryImpl(
return handler.awaitList { download_statQueries.getStatOperations(DownloadStatActionMapper) } return handler.awaitList { download_statQueries.getStatOperations(DownloadStatActionMapper) }
} }
override suspend fun getStatOperationsAsFlow(): Flow<List<DownloadStatOperation>> {
return handler.subscribeToList{ download_statQueries.getStatOperations(DownloadStatActionMapper) }
}
override suspend fun insert(operation: DownloadStatOperation) { override suspend fun insert(operation: DownloadStatOperation) {
handler.await { handler.await {
download_statQueries.insert( download_statQueries.insert(

View file

@ -1,5 +1,6 @@
package tachiyomi.domain.stat.interactor package tachiyomi.domain.stat.interactor
import kotlinx.coroutines.flow.Flow
import tachiyomi.domain.stat.model.DownloadStatOperation import tachiyomi.domain.stat.model.DownloadStatOperation
import tachiyomi.domain.stat.repository.DownloadStatRepository import tachiyomi.domain.stat.repository.DownloadStatRepository
@ -10,4 +11,8 @@ class GetDownloadStatOperations(
suspend fun await(): List<DownloadStatOperation> { suspend fun await(): List<DownloadStatOperation> {
return repository.getStatOperations() return repository.getStatOperations()
} }
suspend fun subscribe(): Flow<List<DownloadStatOperation>> {
return repository.getStatOperationsAsFlow()
}
} }

View file

@ -1,10 +1,13 @@
package tachiyomi.domain.stat.repository package tachiyomi.domain.stat.repository
import kotlinx.coroutines.flow.Flow
import tachiyomi.domain.stat.model.DownloadStatOperation import tachiyomi.domain.stat.model.DownloadStatOperation
interface DownloadStatRepository { interface DownloadStatRepository {
suspend fun getStatOperations(): List<DownloadStatOperation> suspend fun getStatOperations(): List<DownloadStatOperation>
suspend fun getStatOperationsAsFlow(): Flow<List<DownloadStatOperation>>
suspend fun insert(operation: DownloadStatOperation) suspend fun insert(operation: DownloadStatOperation)
} }