From 365d259e944f1b5467178d527180b1a336a63255 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani <46041660+null2264@users.noreply.github.com> Date: Mon, 23 Dec 2024 07:07:11 +0700 Subject: [PATCH] refactor(library): Utilise flow even more (#272) * revert: "revert: "refactor(library): Some adjustments"" This reverts commit 2b639d063069e57d59df4aebeca89eabd902697f. * fix: Don't use emptyFlow * fix: Fix build * fix: Don't overwrite allCategories inside `combine {}` * fix: Fix build --- .../tachiyomi/ui/library/LibraryController.kt | 20 +-- .../tachiyomi/ui/library/LibraryPresenter.kt | 128 ++++++++++-------- .../ui/library/display/LibraryCategoryView.kt | 10 +- .../ui/library/filter/FilterBottomSheet.kt | 6 +- 4 files changed, 87 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 2493614196..bf10138a82 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -557,7 +557,7 @@ open class LibraryController( } presenter.groupType = item shouldScrollToTop = true - presenter.getLibrary() + presenter.updateLibrary() true }.show() } @@ -1056,7 +1056,7 @@ open class LibraryController( if (type.isEnter) { binding.filterBottomSheet.filterBottomSheet.isVisible = true if (type == ControllerChangeType.POP_ENTER) { - presenter.getLibrary() + presenter.updateLibrary() isPoppingIn = true } binding.recyclerCover.isClickable = false @@ -1095,7 +1095,7 @@ open class LibraryController( if (!isBindingInitialized) return updateFilterSheetY() if (observeLater) { - presenter.getLibrary() + presenter.updateLibrary() } } @@ -1408,7 +1408,7 @@ open class LibraryController( private fun onRefresh() { showCategories(false) - presenter.getLibrary() + presenter.updateLibrary() destroyActionModeIfNeeded() } @@ -1432,14 +1432,14 @@ open class LibraryController( val isShowAllCategoriesSet = preferences.showAllCategories().get() if (!query.isNullOrBlank() && this.query.isBlank() && !isShowAllCategoriesSet) { presenter.forceShowAllCategories = preferences.showAllCategoriesWhenSearchingSingleCategory().get() - presenter.getLibrary() + presenter.updateLibrary() } else if (query.isNullOrBlank() && this.query.isNotBlank() && !isShowAllCategoriesSet) { if (!isSubClass) { preferences.showAllCategoriesWhenSearchingSingleCategory() .set(presenter.forceShowAllCategories) } presenter.forceShowAllCategories = false - presenter.getLibrary() + presenter.updateLibrary() } if (query != this.query && !query.isNullOrBlank()) { @@ -1641,7 +1641,7 @@ open class LibraryController( if (mangaId == null) { adapter.getHeaderPositions().forEach { adapter.notifyItemChanged(it) } } else { - presenter.updateManga() + presenter.updateLibrary() } } @@ -1820,7 +1820,7 @@ open class LibraryController( val category = (adapter.getItem(position) as? LibraryHeaderItem)?.category ?: return if (!category.isDynamic) { ManageCategoryDialog(category) { - presenter.getLibrary() + presenter.updateLibrary() }.showDialog(router) } } @@ -1913,7 +1913,7 @@ open class LibraryController( isGone = true setOnClickListener { presenter.forceShowAllCategories = !presenter.forceShowAllCategories - presenter.getLibrary() + presenter.updateLibrary() isSelected = presenter.forceShowAllCategories } val pad = 12.dpToPx @@ -2193,7 +2193,7 @@ open class LibraryController( val activity = activity ?: return viewScope.launchIO { selectedMangas.toList().moveCategories(activity) { - presenter.getLibrary() + presenter.updateLibrary() destroyActionModeIfNeeded() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 77ed4ca035..1c141d99a2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.removeCover import eu.kanade.tachiyomi.data.database.models.seriesType +import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.DelayedLibrarySuggestionsJob import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -51,11 +52,9 @@ import kotlin.random.Random import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.retry import kotlinx.coroutines.flow.shareIn @@ -89,6 +88,7 @@ class LibraryPresenter( private val preferences: PreferencesHelper = Injekt.get(), private val coverCache: CoverCache = Injekt.get(), val sourceManager: SourceManager = Injekt.get(), + private val downloadCache: DownloadCache = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(), private val chapterFilter: ChapterFilter = Injekt.get(), private val trackManager: TrackManager = Injekt.get(), @@ -103,10 +103,7 @@ class LibraryPresenter( private val getTrack: GetTrack by injectLazy() private val getHistory: GetHistory by injectLazy() - private val _fetchLibrary: Channel = Channel(Channel.UNLIMITED) - val fetchLibrary = _fetchLibrary.receiveAsFlow() - .onStart { emit(Unit) } - .shareIn(presenterScope, SharingStarted.Lazily, 1) + private val forceUpdateEvent: Channel = Channel(Channel.UNLIMITED) private val context = preferences.context private val viewContext @@ -129,6 +126,8 @@ class LibraryPresenter( private var allCategories: List = emptyList() /** List of all manga to update the */ + // TODO: Store sectioned before flattening it out for "show all categories" + private var currentLibrary: Map> = mapOf() var libraryItems: List = emptyList() private var sectionedLibraryItems: MutableMap> = mutableMapOf() var currentCategory = -1 @@ -197,6 +196,7 @@ class LibraryPresenter( } subscribeLibrary() + updateLibrary() if (!preferences.showLibrarySearchSuggestions().isSet()) { DelayedLibrarySuggestionsJob.setupTask(context, true) @@ -233,38 +233,29 @@ class LibraryPresenter( combine( getLibraryFlow(), - fetchLibrary, - ) { data, _ -> + downloadCache.changes, + ) { data, _ -> data }.collectLatest { data -> categories = data.categories allCategories = data.allCategories - data.items + val library = data.items + val hiddenItems = library.filter { it.manga.isHidden() }.mapNotNull { it.manga.items }.flatten() + + setDownloadCount(library) + setUnreadBadge(library) + setSourceLanguage(library) + setDownloadCount(hiddenItems) + setUnreadBadge(hiddenItems) + setSourceLanguage(hiddenItems) + + allLibraryItems = library + hiddenLibraryItems = hiddenItems + val mangaMap = library + .applyFilters() + .applySort() + val freshStart = libraryItems.isEmpty() + sectionLibrary(mangaMap, freshStart) } - .collectLatest { library -> - val hiddenItems = library.filter { it.manga.isHidden() }.mapNotNull { it.manga.items }.flatten() - - setDownloadCount(library) - setUnreadBadge(library) - setSourceLanguage(library) - setDownloadCount(hiddenItems) - setUnreadBadge(hiddenItems) - setSourceLanguage(hiddenItems) - - allLibraryItems = library - hiddenLibraryItems = hiddenItems - val mangaMap = library - .applyFilters() - .applySort() - val freshStart = libraryItems.isEmpty() - sectionLibrary(mangaMap, freshStart) - } - } - } - - /** Get favorited manga for library and sort and filter it */ - fun getLibrary() { - presenterScope.launch { - _fetchLibrary.send(Unit) } } @@ -321,8 +312,7 @@ class LibraryPresenter( private suspend fun sectionLibrary(items: List, freshStart: Boolean = false) { libraryItems = items - val showAll = showAllCategories || !libraryIsGrouped || - categories.size <= 1 + val showAll = showAllCategories || !libraryIsGrouped || categories.size <= 1 sectionedLibraryItems = items.groupBy { it.header.category.id ?: 0 }.toMutableMap() if (!showAll && currentCategory == -1) { currentCategory = categories.find { @@ -758,6 +748,9 @@ class LibraryPresenter( preferences.librarySortingMode().changes(), preferences.librarySortingAscending().changes(), + + preferences.collapsedCategories().changes(), + preferences.collapsedDynamicCategories().changes(), ) { ItemPreferences( filterDownloaded = it[0] as Int, @@ -771,6 +764,8 @@ class LibraryPresenter( showAllCategories = it[8] as Boolean, sortingMode = it[9] as Int, sortAscending = it[10] as Boolean, + collapsedCategories = it[11] as Set, + collapsedDynamicCategories = it[12] as Set, ) } @@ -808,26 +803,27 @@ class LibraryPresenter( * If category id '-1' is not empty, it means the library not grouped by categories */ private fun getLibraryFlow(): Flow { - return combine( + val libraryFlow = combine( getCategories.subscribe(), // FIXME: Remove retry once a real solution is found getLibraryManga.subscribe().retry(1) { e -> e is NullPointerException }, getPreferencesFlow(), - preferences.removeArticles().changes(), - fetchLibrary - ) { dbCategories, libraryMangaList, prefs, removeArticles, _ -> + forceUpdateEvent.receiveAsFlow(), + ) { dbCategories, libraryMangaList, prefs, _ -> groupType = prefs.groupType val defaultCategory = createDefaultCategory() val allCategories = listOf(defaultCategory) + dbCategories - val (items, categories, hiddenItems) = if (groupType <= BY_DEFAULT || !libraryIsGrouped) { + // FIXME: Should return Map where Int is category id + if (groupType <= BY_DEFAULT || !libraryIsGrouped) { getLibraryItems( - dbCategories, + allCategories, // FIXME: Don't depends on allCategories libraryMangaList, prefs.sortingMode, prefs.sortAscending, prefs.showAllCategories, + prefs.collapsedCategories, defaultCategory, ) } else { @@ -836,8 +832,17 @@ class LibraryPresenter( prefs.sortingMode, prefs.sortAscending, groupType, + prefs.collapsedDynamicCategories, ) - } + } to allCategories + } + + return combine( + libraryFlow, + preferences.removeArticles().changes(), + ) { library, removeArticles -> + val (libraryItems, allCategories) = library + val (items, categories, hiddenItems) = libraryItems LibraryData( categories = categories, @@ -855,6 +860,7 @@ class LibraryPresenter( sortingMode: Int, isAscending: Boolean, showAll: Boolean, + collapsedCategories: Set, defaultCategory: Category, ): Triple, List, List> { val categories = allCategories.toMutableList() @@ -878,6 +884,11 @@ class LibraryPresenter( } + (-1 to catItemAll) + (0 to LibraryHeaderItem({ categories.getOrDefault(0) }, 0)) ).toMap() + // TODO + val map = libraryManga.groupBy { + categories.getOrDefault(it.category) + } + val items = if (libraryIsGrouped) { libraryManga } else { @@ -897,16 +908,14 @@ class LibraryPresenter( val categoriesHidden = if (forceShowAllCategories || controllerIsSubClass) { emptySet() } else { - preferences.collapsedCategories().get().mapNotNull { it.toIntOrNull() }.toSet() + collapsedCategories.mapNotNull { it.toIntOrNull() }.toSet() } if (categorySet.contains(0)) categories.add(0, defaultCategory) if (libraryIsGrouped) { categories.forEach { category -> val catId = category.id ?: return@forEach - if (catId > 0 && !categorySet.contains(catId) && - (catId !in categoriesHidden || !showAll) - ) { + if (catId > 0 && !categorySet.contains(catId) && (catId !in categoriesHidden || !showAll)) { val headerItem = headerItems[catId] if (headerItem != null) { items.add( @@ -959,6 +968,7 @@ class LibraryPresenter( sortingMode: Int, isAscending: Boolean, groupType: Int, + collapsedDynamicCategories: Set, ): Triple, List, List> { val tagItems: MutableMap = mutableMapOf() @@ -1061,7 +1071,7 @@ class LibraryPresenter( val hiddenDynamics = if (controllerIsSubClass) { emptySet() } else { - preferences.collapsedDynamicCategories().get() + collapsedDynamicCategories } val headers = tagItems.map { item -> Category.createCustom( @@ -1217,7 +1227,6 @@ class LibraryPresenter( .mapNotNull { if (it.id != null) MangaUpdate(it.id!!, favorite = false) else null } withIOContext { updateManga.awaitAll(mangaToDelete) } - getLibrary() } } @@ -1240,8 +1249,11 @@ class LibraryPresenter( } } - /** Called when Library Service updates a manga, update the item as well */ - fun updateManga() = getLibrary() + /** Force update the library */ + fun updateLibrary() = presenterScope.launch { + forceUpdateEvent.send(Unit) + } + /** Undo the removal of the manga once in library */ fun reAddMangas(mangas: List) { @@ -1251,12 +1263,12 @@ class LibraryPresenter( withIOContext { updateManga.awaitAll(mangaToAdd) } (view as? FilteredLibraryController)?.updateStatsPage() - getLibrary() } } /** Returns first unread chapter of a manga */ fun getFirstUnread(manga: Manga): Chapter? { + // FIXME: Don't do blocking val chapters = runBlocking { getChapter.awaitAll(manga) } return ChapterSort(manga, chapterFilter, preferences).getNextUnreadChapter(chapters, false) } @@ -1308,7 +1320,6 @@ class LibraryPresenter( } } - // TODO: Use SQLDelight /** Shift a manga's category via drag & drop */ fun moveMangaToCategory( manga: LibraryManga, @@ -1354,7 +1365,7 @@ class LibraryPresenter( ) } } - getLibrary() + updateLibrary() } } @@ -1389,7 +1400,6 @@ class LibraryPresenter( } preferences.collapsedDynamicCategories().set(categoriesHidden) } - getLibrary() } private fun getDynamicCategoryName(category: Category): String = @@ -1420,7 +1430,6 @@ class LibraryPresenter( } } } - getLibrary() } fun allCategoriesExpanded(): Boolean { @@ -1462,7 +1471,7 @@ class LibraryPresenter( mapMangaChapters[manga] = chapters } - getLibrary() + updateLibrary() } return mapMangaChapters } @@ -1478,7 +1487,7 @@ class LibraryPresenter( } }.flatten() updateChapter.awaitAll(updates) - getLibrary() + updateLibrary() } } @@ -1651,6 +1660,9 @@ class LibraryPresenter( val sortingMode: Int, val sortAscending: Boolean, + + val collapsedCategories: Set, + val collapsedDynamicCategories: Set, ) data class LibraryData( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt index b409856977..0dc927b9e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt @@ -2,16 +2,14 @@ package eu.kanade.tachiyomi.ui.library.display import android.content.Context import android.util.AttributeSet -import eu.kanade.tachiyomi.R -import yokai.i18n.MR -import yokai.util.lang.getString -import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.databinding.LibraryCategoryLayoutBinding import eu.kanade.tachiyomi.util.bindToPreference import eu.kanade.tachiyomi.util.lang.withSubtitle import eu.kanade.tachiyomi.util.system.toInt import eu.kanade.tachiyomi.widget.BaseLibraryDisplayView import kotlin.math.min +import yokai.i18n.MR +import yokai.util.lang.getString class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : BaseLibraryDisplayView(context, attrs) { @@ -20,7 +18,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att override fun initGeneralPreferences() { with(binding) { showAll.bindToPreference(preferences.showAllCategories()) { - controller?.presenter?.getLibrary() + controller?.presenter?.updateLibrary() binding.categoryShow.isEnabled = it } categoryShow.isEnabled = showAll.isChecked @@ -30,7 +28,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att dynamicToBottom.text = context.getString(MR.strings.move_dynamic_to_bottom) .withSubtitle(context, MR.strings.when_grouping_by_sources_tags) dynamicToBottom.bindToPreference(preferences.collapsedDynamicAtBottom()) { - controller?.presenter?.getLibrary() + controller?.presenter?.updateLibrary() } showEmptyCatsFiltering.bindToPreference(preferences.showEmptyCategoriesWhileFiltering()) { controller?.presenter?.requestFilterUpdate() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt index dbf17c3c49..9950858541 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt @@ -36,6 +36,8 @@ import eu.kanade.tachiyomi.util.view.isCollapsed import eu.kanade.tachiyomi.util.view.isExpanded import eu.kanade.tachiyomi.util.view.isHidden import eu.kanade.tachiyomi.util.view.setText +import kotlin.math.max +import kotlin.math.roundToInt import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.drop @@ -48,8 +50,6 @@ import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import yokai.i18n.MR import yokai.util.lang.getString -import kotlin.math.max -import kotlin.math.roundToInt class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs), @@ -637,7 +637,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri SeriesType('m', MR.strings.series_type), Bookmarked('b', MR.strings.bookmarked), Tracked('t', MR.strings.tracking), - ContentType('s', MR.strings.content_type); + ContentType('s', MR.strings.content_type) ; companion object {