Rework favourite sheet
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<List<Long>>
|
||||
|
||||
@Query("SELECT DISTINCT category_id FROM favourites WHERE manga_id IN (:mangaIds) AND deleted_at = 0")
|
||||
abstract suspend fun findCategoriesIds(mangaIds: Collection<Long>): List<Long>
|
||||
|
||||
/** 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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,10 @@ class FavouritesRepository @Inject constructor(
|
||||
return db.getFavouriteCategoriesDao().find(id.toInt()).toFavouriteCategory()
|
||||
}
|
||||
|
||||
suspend fun getCategoriesIds(mangaIds: Collection<Long>): Set<Long> {
|
||||
return db.getFavouritesDao().findCategoriesIds(mangaIds).toSet()
|
||||
}
|
||||
|
||||
suspend fun createCategory(
|
||||
title: String,
|
||||
sortOrder: ListSortOrder,
|
||||
|
||||
@@ -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<SheetFavoriteCategoriesBinding>(),
|
||||
OnListItemClickListener<MangaCategoryItem> {
|
||||
class FavoriteSheet : BaseAdaptiveSheet<SheetFavoriteCategoriesBinding>(), OnListItemClickListener<MangaCategoryItem> {
|
||||
|
||||
private val viewModel: MangaCategoriesViewModel by viewModels()
|
||||
|
||||
private var adapter: MangaCategoriesAdapter? = null
|
||||
private val viewModel by viewModels<FavoriteSheetViewModel>()
|
||||
|
||||
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<ListModel>) {
|
||||
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<Manga>) =
|
||||
FavouriteSheet().withArgs(1) {
|
||||
putParcelableArrayList(
|
||||
KEY_MANGA_LIST,
|
||||
manga.mapTo(ArrayList(manga.size)) {
|
||||
ParcelableManga(it)
|
||||
},
|
||||
)
|
||||
}.showDistinct(fm, TAG)
|
||||
fun show(fm: FragmentManager, manga: Collection<Manga>) = FavoriteSheet().withArgs(1) {
|
||||
putParcelableArrayList(
|
||||
KEY_MANGA_LIST,
|
||||
manga.mapTo(ArrayList(manga.size), ::ParcelableManga),
|
||||
)
|
||||
}.showDistinct(fm, TAG)
|
||||
}
|
||||
}
|
||||
@@ -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<List<ParcelableManga>>(KEY_MANGA_LIST).map { it.manga }
|
||||
private val manga = savedStateHandle.require<List<ParcelableManga>>(FavoriteSheet.KEY_MANGA_LIST).mapToSet {
|
||||
it.manga
|
||||
}
|
||||
private val header = CategoriesHeaderItem()
|
||||
|
||||
val content: StateFlow<List<ListModel>> = combine(
|
||||
private val checkedCategories = MutableStateFlow<Set<Long>?>(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<Long>()
|
||||
var isFirst = true
|
||||
for (ids in array) {
|
||||
if (isFirst) {
|
||||
result.addAll(ids)
|
||||
isFirst = false
|
||||
} else {
|
||||
result.retainAll(ids.toSet())
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -21,10 +21,16 @@
|
||||
android:title="@string/save"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_favourite"
|
||||
android:icon="@drawable/ic_heart"
|
||||
android:title="@string/categories"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_select_all"
|
||||
android:icon="?actionModeSelectAllDrawable"
|
||||
android:title="@android:string/selectAll"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
</menu>
|
||||
</menu>
|
||||
|
||||
Reference in New Issue
Block a user