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 56092ddddd..77a468109c 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 @@ -558,7 +558,7 @@ open class LibraryController( } presenter.groupType = item shouldScrollToTop = true - presenter.updateLibrary() + presenter.getLibrary() true }.show() } @@ -1055,7 +1055,7 @@ open class LibraryController( if (type.isEnter) { binding.filterBottomSheet.filterBottomSheet.isVisible = true if (type == ControllerChangeType.POP_ENTER) { - presenter.updateLibrary() + presenter.getLibrary() isPoppingIn = true } DownloadJob.callListeners() @@ -1095,7 +1095,7 @@ open class LibraryController( if (!isBindingInitialized) return updateFilterSheetY() if (observeLater) { - presenter.updateLibrary() + presenter.getLibrary() } } @@ -1408,7 +1408,7 @@ open class LibraryController( private fun onRefresh() { showCategories(false) - presenter.updateLibrary() + presenter.getLibrary() 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.updateLibrary() + presenter.getLibrary() } else if (query.isNullOrBlank() && this.query.isNotBlank() && !isShowAllCategoriesSet) { if (!isSubClass) { preferences.showAllCategoriesWhenSearchingSingleCategory() .set(presenter.forceShowAllCategories) } presenter.forceShowAllCategories = false - presenter.updateLibrary() + presenter.getLibrary() } if (query != this.query && !query.isNullOrBlank()) { @@ -1641,7 +1641,7 @@ open class LibraryController( if (mangaId == null) { adapter.getHeaderPositions().forEach { adapter.notifyItemChanged(it) } } else { - presenter.updateLibrary() + presenter.updateManga() } } @@ -1820,7 +1820,7 @@ open class LibraryController( val category = (adapter.getItem(position) as? LibraryHeaderItem)?.category ?: return if (!category.isDynamic) { ManageCategoryDialog(category) { - presenter.updateLibrary() + presenter.getLibrary() }.showDialog(router) } } @@ -1913,7 +1913,7 @@ open class LibraryController( isGone = true setOnClickListener { presenter.forceShowAllCategories = !presenter.forceShowAllCategories - presenter.updateLibrary() + presenter.getLibrary() isSelected = presenter.forceShowAllCategories } val pad = 12.dpToPx @@ -2192,7 +2192,7 @@ open class LibraryController( private fun showChangeMangaCategoriesSheet() { val activity = activity ?: return selectedMangas.toList().moveCategories(activity) { - presenter.updateLibrary() + presenter.getLibrary() 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 fe3bf0530c..b024ac77ad 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 @@ -54,11 +54,13 @@ 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.emptyFlow import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext @@ -103,7 +105,10 @@ class LibraryPresenter( private val getTrack: GetTrack by injectLazy() private val getHistory: GetHistory by injectLazy() - private val forceUpdateEvent: Channel = Channel(Channel.UNLIMITED) + private val _fetchLibrary: Channel = Channel(Channel.UNLIMITED) + val fetchLibrary = _fetchLibrary.receiveAsFlow() + .onStart { emit(Unit) } + .shareIn(presenterScope, SharingStarted.Lazily, 1) private val context = preferences.context private val viewContext @@ -126,8 +131,6 @@ 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 @@ -196,7 +199,6 @@ class LibraryPresenter( } subscribeLibrary() - updateLibrary() if (!preferences.showLibrarySearchSuggestions().isSet()) { DelayedLibrarySuggestionsJob.setupTask(context, true) @@ -233,7 +235,7 @@ class LibraryPresenter( combine( getLibraryFlow(), - emptyFlow(), // Placeholder flow for later usage + fetchLibrary, ) { data, _ -> categories = data.categories allCategories = data.allCategories @@ -261,6 +263,13 @@ class LibraryPresenter( } } + /** Get favorited manga for library and sort and filter it */ + fun getLibrary() { + presenterScope.launch { + _fetchLibrary.send(Unit) + } + } + private suspend fun reorderCategories(categories: List) { val sortedCategories = categories.sortedBy { it.order } sortedCategories.forEachIndexed { i, category -> category.order = i } @@ -314,7 +323,8 @@ 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 { @@ -750,9 +760,6 @@ class LibraryPresenter( preferences.librarySortingMode().changes(), preferences.librarySortingAscending().changes(), - - preferences.collapsedCategories().changes(), - preferences.collapsedDynamicCategories().changes(), ) { ItemPreferences( filterDownloaded = it[0] as Int, @@ -766,8 +773,6 @@ 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, ) } @@ -805,24 +810,22 @@ class LibraryPresenter( * If category id '-1' is not empty, it means the library not grouped by categories */ private fun getLibraryFlow(): Flow { - val libraryItemFlow = combine( + return combine( getCategories.subscribe(), getLibraryManga.subscribe(), getPreferencesFlow(), - forceUpdateEvent.receiveAsFlow(), - ) { allCategories, libraryMangaList, prefs, _ -> - this.groupType = prefs.groupType - this.allCategories = allCategories + preferences.removeArticles().changes(), + fetchLibrary + ) { allCategories, libraryMangaList, prefs, removeArticles, _ -> + groupType = prefs.groupType - // FIXME: Should return Map where Int is category id - if (groupType <= BY_DEFAULT || !libraryIsGrouped) { + val (items, categories, hiddenItems) = if (groupType <= BY_DEFAULT || !libraryIsGrouped) { getLibraryItems( - allCategories, // FIXME: Don't depends on allCategories + allCategories, libraryMangaList, prefs.sortingMode, prefs.sortAscending, prefs.showAllCategories, - prefs.collapsedCategories, ) } else { getDynamicLibraryItems( @@ -830,16 +833,8 @@ class LibraryPresenter( prefs.sortingMode, prefs.sortAscending, groupType, - prefs.collapsedDynamicCategories, ) } - } - - return combine( - libraryItemFlow, - preferences.removeArticles().changes(), - ) { libraryItems, removeArticles -> - val (items, categories, hiddenItems) = libraryItems LibraryData( categories = categories, @@ -857,7 +852,6 @@ class LibraryPresenter( sortingMode: Int, isAscending: Boolean, showAll: Boolean, - collapsedCategories: Set, ): Triple, List, List> { val categories = allCategories.toMutableList() val hiddenItems = mutableListOf() @@ -880,11 +874,6 @@ 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 { @@ -904,14 +893,16 @@ class LibraryPresenter( val categoriesHidden = if (forceShowAllCategories || controllerIsSubClass) { emptySet() } else { - collapsedCategories.mapNotNull { it.toIntOrNull() }.toSet() + preferences.collapsedCategories().get().mapNotNull { it.toIntOrNull() }.toSet() } if (categorySet.contains(0)) categories.add(0, createDefaultCategory()) 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( @@ -964,7 +955,6 @@ class LibraryPresenter( sortingMode: Int, isAscending: Boolean, groupType: Int, - collapsedDynamicCategories: Set, ): Triple, List, List> { val tagItems: MutableMap = mutableMapOf() @@ -1067,7 +1057,7 @@ class LibraryPresenter( val hiddenDynamics = if (controllerIsSubClass) { emptySet() } else { - collapsedDynamicCategories + preferences.collapsedDynamicCategories().get() } val headers = tagItems.map { item -> Category.createCustom( @@ -1223,6 +1213,7 @@ class LibraryPresenter( .mapNotNull { if (it.id != null) MangaUpdate(it.id!!, favorite = false) else null } withIOContext { updateManga.awaitAll(mangaToDelete) } + getLibrary() } } @@ -1245,11 +1236,8 @@ class LibraryPresenter( } } - /** Force update the library */ - fun updateLibrary() = presenterScope.launch { - forceUpdateEvent.send(Unit) - } - + /** Called when Library Service updates a manga, update the item as well */ + fun updateManga() = getLibrary() /** Undo the removal of the manga once in library */ fun reAddMangas(mangas: List) { @@ -1259,12 +1247,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) } @@ -1316,6 +1304,7 @@ class LibraryPresenter( } } + // TODO: Use SQLDelight /** Shift a manga's category via drag & drop */ fun moveMangaToCategory( manga: LibraryManga, @@ -1361,7 +1350,7 @@ class LibraryPresenter( ) } } - updateLibrary() + getLibrary() } } @@ -1396,6 +1385,7 @@ class LibraryPresenter( } preferences.collapsedDynamicCategories().set(categoriesHidden) } + getLibrary() } private fun getDynamicCategoryName(category: Category): String = @@ -1426,6 +1416,7 @@ class LibraryPresenter( } } } + getLibrary() } fun allCategoriesExpanded(): Boolean { @@ -1467,7 +1458,7 @@ class LibraryPresenter( mapMangaChapters[manga] = chapters } - updateLibrary() + getLibrary() } return mapMangaChapters } @@ -1483,7 +1474,7 @@ class LibraryPresenter( } }.flatten() updateChapter.awaitAll(updates) - updateLibrary() + getLibrary() } } @@ -1663,9 +1654,6 @@ 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 0dc927b9e0..b409856977 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,14 +2,16 @@ 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) { @@ -18,7 +20,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att override fun initGeneralPreferences() { with(binding) { showAll.bindToPreference(preferences.showAllCategories()) { - controller?.presenter?.updateLibrary() + controller?.presenter?.getLibrary() binding.categoryShow.isEnabled = it } categoryShow.isEnabled = showAll.isChecked @@ -28,7 +30,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?.updateLibrary() + controller?.presenter?.getLibrary() } 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 9950858541..dbf17c3c49 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,8 +36,6 @@ 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 @@ -50,6 +48,8 @@ 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 {