From 73143d2f94c1b0b30d21e3a61cdb7a19d0e826e9 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 11 Nov 2023 14:37:16 +0200 Subject: [PATCH] Rework favourite sheet --- .../kotatsu/details/ui/DetailsMenuProvider.kt | 4 +- .../kotatsu/favourites/data/FavouritesDao.kt | 4 ++ .../favourites/domain/FavouritesRepository.kt | 4 ++ .../{FavouriteSheet.kt => FavoriteSheet.kt} | 41 ++++-------- ...ViewModel.kt => FavoriteSheetViewModel.kt} | 65 +++++++++---------- .../kotatsu/list/ui/MangaListFragment.kt | 4 +- .../search/ui/multi/MultiSearchActivity.kt | 5 +- app/src/main/res/menu/mode_favourites.xml | 8 ++- 8 files changed, 62 insertions(+), 73 deletions(-) rename app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/{FavouriteSheet.kt => FavoriteSheet.kt} (66%) rename app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/{MangaCategoriesViewModel.kt => FavoriteSheetViewModel.kt} (60%) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt index 4614921d8..2f9f2e667 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt @@ -19,7 +19,7 @@ import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.download.ui.dialog.DownloadOption -import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteSheet +import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteSheet import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity @@ -63,7 +63,7 @@ class DetailsMenuProvider( R.id.action_favourite -> { viewModel.manga.value?.let { - FavouriteSheet.show(activity.supportFragmentManager, it) + FavoriteSheet.show(activity.supportFragmentManager, it) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt index 21b771b69..deb4b822d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt @@ -106,6 +106,9 @@ abstract class FavouritesDao { @Query("SELECT DISTINCT category_id FROM favourites WHERE manga_id = :id AND deleted_at = 0") abstract fun observeIds(id: Long): Flow> + @Query("SELECT DISTINCT category_id FROM favourites WHERE manga_id IN (:mangaIds) AND deleted_at = 0") + abstract suspend fun findCategoriesIds(mangaIds: Collection): List + /** INSERT **/ @Insert(onConflict = OnConflictStrategy.REPLACE) @@ -171,6 +174,7 @@ abstract class FavouritesDao { ListSortOrder.NEW_CHAPTERS -> "(SELECT chapters_new FROM tracks WHERE tracks.manga_id = manga.manga_id) DESC" ListSortOrder.UPDATED, // for legacy support ListSortOrder.PROGRESS -> "(SELECT percent FROM history WHERE history.manga_id = manga.manga_id) DESC" + else -> throw IllegalArgumentException("Sort order $sortOrder is not supported") } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index 4069cb3c8..5b5fa094f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -102,6 +102,10 @@ class FavouritesRepository @Inject constructor( return db.getFavouriteCategoriesDao().find(id.toInt()).toFavouriteCategory() } + suspend fun getCategoriesIds(mangaIds: Collection): Set { + return db.getFavouritesDao().findCategoriesIds(mangaIds).toSet() + } + suspend fun createCategory( title: String, sortOrder: ListSortOrder, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavouriteSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteSheet.kt similarity index 66% rename from app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavouriteSheet.kt rename to app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteSheet.kt index f35629c2b..52e1762c4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavouriteSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteSheet.kt @@ -19,17 +19,12 @@ import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.databinding.SheetFavoriteCategoriesBinding import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem -import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.Manga @AndroidEntryPoint -class FavouriteSheet : - BaseAdaptiveSheet(), - OnListItemClickListener { +class FavoriteSheet : BaseAdaptiveSheet(), OnListItemClickListener { - private val viewModel: MangaCategoriesViewModel by viewModels() - - private var adapter: MangaCategoriesAdapter? = null + private val viewModel by viewModels() override fun onCreateViewBinding( inflater: LayoutInflater, @@ -41,44 +36,32 @@ class FavouriteSheet : savedInstanceState: Bundle?, ) { super.onViewBindingCreated(binding, savedInstanceState) - adapter = MangaCategoriesAdapter(this) + val adapter = MangaCategoriesAdapter(this) binding.recyclerViewCategories.adapter = adapter - viewModel.content.observe(viewLifecycleOwner, this::onContentChanged) + viewModel.content.observe(viewLifecycleOwner, adapter) viewModel.onError.observeEvent(viewLifecycleOwner, ::onError) } - override fun onDestroyView() { - adapter = null - super.onDestroyView() - } - override fun onItemClick(item: MangaCategoryItem, view: View) { viewModel.setChecked(item.category.id, !item.isChecked) } - private fun onContentChanged(categories: List) { - adapter?.items = categories - } - private fun onError(e: Throwable) { Toast.makeText(context ?: return, e.getDisplayMessage(resources), Toast.LENGTH_SHORT).show() } companion object { - private const val TAG = "FavouriteCategoriesDialog" + private const val TAG = "FavoriteSheet" const val KEY_MANGA_LIST = "manga_list" - fun show(fm: FragmentManager, manga: Manga) = Companion.show(fm, listOf(manga)) + fun show(fm: FragmentManager, manga: Manga) = show(fm, setOf(manga)) - fun show(fm: FragmentManager, manga: Collection) = - FavouriteSheet().withArgs(1) { - putParcelableArrayList( - KEY_MANGA_LIST, - manga.mapTo(ArrayList(manga.size)) { - ParcelableManga(it) - }, - ) - }.showDistinct(fm, TAG) + fun show(fm: FragmentManager, manga: Collection) = FavoriteSheet().withArgs(1) { + putParcelableArrayList( + KEY_MANGA_LIST, + manga.mapTo(ArrayList(manga.size), ::ParcelableManga), + ) + }.showDistinct(fm, TAG) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/MangaCategoriesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteSheetViewModel.kt similarity index 60% rename from app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/MangaCategoriesViewModel.kt rename to app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteSheetViewModel.kt index 7d8c940a1..2a9c85daf 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/MangaCategoriesViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteSheetViewModel.kt @@ -4,77 +4,70 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus import org.koitharu.kotatsu.core.model.ids import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.ui.BaseViewModel +import org.koitharu.kotatsu.core.util.ext.firstNotNull import org.koitharu.kotatsu.core.util.ext.require import org.koitharu.kotatsu.favourites.domain.FavouritesRepository -import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteSheet.Companion.KEY_MANGA_LIST import org.koitharu.kotatsu.favourites.ui.categories.select.model.CategoriesHeaderItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem -import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.parsers.util.mapToSet import javax.inject.Inject @HiltViewModel -class MangaCategoriesViewModel @Inject constructor( +class FavoriteSheetViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val favouritesRepository: FavouritesRepository, settings: AppSettings, ) : BaseViewModel() { - private val manga = savedStateHandle.require>(KEY_MANGA_LIST).map { it.manga } + private val manga = savedStateHandle.require>(FavoriteSheet.KEY_MANGA_LIST).mapToSet { + it.manga + } private val header = CategoriesHeaderItem() - - val content: StateFlow> = combine( + private val checkedCategories = MutableStateFlow?>(null) + val content = combine( favouritesRepository.observeCategories(), - observeCategoriesIds(), - ) { all, checked -> - buildList(all.size + 1) { + checkedCategories.filterNotNull(), + settings.observeAsFlow(AppSettings.KEY_TRACKER_ENABLED) { isTrackerEnabled }, + ) { categories, checked, tracker -> + buildList(categories.size + 1) { add(header) - all.mapTo(this) { + categories.mapTo(this) { cat -> MangaCategoryItem( - category = it, - isChecked = it.id in checked, - isTrackerEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources, + category = cat, + isChecked = cat.id in checked, + isTrackerEnabled = tracker, ) } } - }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList()) + }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(header)) + + init { + launchJob(Dispatchers.Default) { + checkedCategories.value = favouritesRepository.getCategoriesIds(manga.ids()) + } + } fun setChecked(categoryId: Long, isChecked: Boolean) { launchJob(Dispatchers.Default) { + val checkedIds = checkedCategories.firstNotNull() if (isChecked) { + checkedCategories.value = checkedIds + categoryId favouritesRepository.addToCategory(categoryId, manga) } else { + checkedCategories.value = checkedIds - categoryId favouritesRepository.removeFromCategory(categoryId, manga.ids()) } } } - - private fun observeCategoriesIds() = if (manga.size == 1) { - // Fast path - favouritesRepository.observeCategoriesIds(manga[0].id) - } else { - combine( - manga.map { favouritesRepository.observeCategoriesIds(it.id) }, - ) { array -> - val result = HashSet() - var isFirst = true - for (ids in array) { - if (isFirst) { - result.addAll(ids) - isFirst = false - } else { - result.retainAll(ids.toSet()) - } - } - result - } - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt index 4a2a304e3..3d108f12b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -41,7 +41,7 @@ import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver -import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteSheet +import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteSheet import org.koitharu.kotatsu.list.ui.adapter.ListItemType import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter import org.koitharu.kotatsu.list.ui.adapter.MangaListListener @@ -284,7 +284,7 @@ abstract class MangaListFragment : } R.id.action_favourite -> { - FavouriteSheet.show(childFragmentManager, selectedItems) + FavoriteSheet.show(childFragmentManager, selectedItems) mode.finish() true } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt index 245445dc5..48f88d3b5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt @@ -8,7 +8,6 @@ import android.view.MenuItem import android.view.View import androidx.activity.viewModels import androidx.appcompat.view.ActionMode -import androidx.core.content.ContextCompat import androidx.core.graphics.Insets import androidx.core.view.updatePadding import coil.ImageLoader @@ -26,7 +25,7 @@ import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver -import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteSheet +import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteSheet import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration @@ -159,7 +158,7 @@ class MultiSearchActivity : } R.id.action_favourite -> { - FavouriteSheet.show(supportFragmentManager, collectSelectedItems()) + FavoriteSheet.show(supportFragmentManager, collectSelectedItems()) mode.finish() true } diff --git a/app/src/main/res/menu/mode_favourites.xml b/app/src/main/res/menu/mode_favourites.xml index a9aabe62e..954c24faf 100644 --- a/app/src/main/res/menu/mode_favourites.xml +++ b/app/src/main/res/menu/mode_favourites.xml @@ -21,10 +21,16 @@ android:title="@string/save" app:showAsAction="ifRoom|withText" /> + + - \ No newline at end of file +