Fix pages thumbnails loading

This commit is contained in:
Koitharu
2023-10-13 14:26:52 +03:00
parent 5b53f8c27d
commit 97524d66f2
28 changed files with 258 additions and 174 deletions

View File

@@ -2,9 +2,9 @@ package org.koitharu.kotatsu.core.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.SortOrder
import java.util.Date
@Parcelize
@@ -12,7 +12,7 @@ data class FavouriteCategory(
val id: Long,
val title: String,
val sortKey: Int,
val order: SortOrder,
val order: ListSortOrder,
val createdAt: Date,
val isTrackingEnabled: Boolean,
val isVisibleInLibrary: Boolean,

View File

@@ -22,7 +22,7 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.putEnumValue
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.history.domain.model.HistoryOrder
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.find
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
@@ -76,6 +76,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getEnumValue(KEY_LIST_MODE_HISTORY, listMode)
set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE_HISTORY, value) }
var suggestionsListMode: ListMode
get() = prefs.getEnumValue(KEY_LIST_MODE_SUGGESTIONS, listMode)
set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE_SUGGESTIONS, value) }
var favoritesListMode: ListMode
get() = prefs.getEnumValue(KEY_LIST_MODE_FAVORITES, listMode)
set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE_FAVORITES, value) }
@@ -315,8 +319,8 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getEnumValue(KEY_LOCAL_LIST_ORDER, SortOrder.NEWEST)
set(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) }
var historySortOrder: HistoryOrder
get() = prefs.getEnumValue(KEY_HISTORY_ORDER, HistoryOrder.UPDATED)
var historySortOrder: ListSortOrder
get() = prefs.getEnumValue(KEY_HISTORY_ORDER, ListSortOrder.UPDATED)
set(value) = prefs.edit { putEnumValue(KEY_HISTORY_ORDER, value) }
val isRelatedMangaEnabled: Boolean
@@ -417,6 +421,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_LIST_MODE = "list_mode_2"
const val KEY_LIST_MODE_HISTORY = "list_mode_history"
const val KEY_LIST_MODE_FAVORITES = "list_mode_favorites"
const val KEY_LIST_MODE_SUGGESTIONS = "list_mode_suggestions"
const val KEY_THEME = "theme"
const val KEY_COLOR_THEME = "color_theme"
const val KEY_THEME_AMOLED = "amoled_theme"

View File

@@ -55,3 +55,5 @@ inline fun <reified E : Enum<E>> Collection<E>.toEnumSet(): EnumSet<E> = if (isE
} else {
EnumSet.copyOf(this)
}
fun <E : Enum<E>> Collection<E>.sortedByOrdinal() = sortedBy { it.ordinal }

View File

@@ -1,17 +1,16 @@
package org.koitharu.kotatsu.favourites.data
import org.koitharu.kotatsu.core.db.entity.SortOrder
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.list.domain.ListSortOrder
import java.util.Date
fun FavouriteCategoryEntity.toFavouriteCategory(id: Long = categoryId.toLong()) = FavouriteCategory(
id = id,
title = title,
sortKey = sortKey,
order = SortOrder(order, SortOrder.NEWEST),
order = ListSortOrder(order, ListSortOrder.NEWEST),
createdAt = Date(createdAt),
isTrackingEnabled = track,
isVisibleInLibrary = isVisibleInLibrary,

View File

@@ -1,13 +1,19 @@
package org.koitharu.kotatsu.favourites.data
import androidx.room.*
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Transaction
import androidx.room.Upsert
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import kotlinx.coroutines.flow.Flow
import org.intellij.lang.annotations.Language
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.favourites.domain.model.Cover
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.list.domain.ListSortOrder
@Dao
abstract class FavouritesDao {
@@ -22,7 +28,7 @@ abstract class FavouritesDao {
@Query("SELECT * FROM favourites WHERE deleted_at = 0 GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit")
abstract suspend fun findLast(limit: Int): List<FavouriteManga>
fun observeAll(order: SortOrder): Flow<List<FavouriteManga>> {
fun observeAll(order: ListSortOrder): Flow<List<FavouriteManga>> {
val orderBy = getOrderBy(order)
@Language("RoomSql")
@@ -47,7 +53,7 @@ abstract class FavouritesDao {
)
abstract suspend fun findAll(categoryId: Long): List<FavouriteManga>
fun observeAll(categoryId: Long, order: SortOrder): Flow<List<FavouriteManga>> {
fun observeAll(categoryId: Long, order: ListSortOrder): Flow<List<FavouriteManga>> {
val orderBy = getOrderBy(order)
@Language("RoomSql")
@@ -72,7 +78,7 @@ abstract class FavouritesDao {
)
abstract suspend fun findAllManga(categoryId: Int): List<MangaEntity>
suspend fun findCovers(categoryId: Long, order: SortOrder): List<Cover> {
suspend fun findCovers(categoryId: Long, order: ListSortOrder): List<Cover> {
val orderBy = getOrderBy(order)
@Language("RoomSql")
@@ -157,13 +163,13 @@ abstract class FavouritesDao {
@Query("UPDATE favourites SET deleted_at = :deletedAt WHERE category_id = :categoryId AND deleted_at = 0")
protected abstract suspend fun setDeletedAtAll(categoryId: Long, deletedAt: Long)
private fun getOrderBy(sortOrder: SortOrder) = when (sortOrder) {
SortOrder.RATING -> "rating DESC"
SortOrder.NEWEST,
SortOrder.UPDATED,
private fun getOrderBy(sortOrder: ListSortOrder) = when (sortOrder) {
ListSortOrder.RATING -> "rating DESC"
ListSortOrder.NEWEST,
ListSortOrder.UPDATED,
-> "created_at DESC"
SortOrder.ALPHABETICAL -> "title ASC"
ListSortOrder.ALPHABETIC -> "title ASC"
else -> throw IllegalArgumentException("Sort order $sortOrder is not supported")
}
}

View File

@@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.SortOrder
import org.koitharu.kotatsu.core.db.entity.toEntities
import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.core.model.FavouriteCategory
@@ -20,8 +19,8 @@ import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
import org.koitharu.kotatsu.favourites.data.toManga
import org.koitharu.kotatsu.favourites.data.toMangaList
import org.koitharu.kotatsu.favourites.domain.model.Cover
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
import javax.inject.Inject
@@ -41,7 +40,7 @@ class FavouritesRepository @Inject constructor(
return entities.toMangaList()
}
fun observeAll(order: SortOrder): Flow<List<Manga>> {
fun observeAll(order: ListSortOrder): Flow<List<Manga>> {
return db.favouritesDao.observeAll(order)
.mapItems { it.toManga() }
}
@@ -51,7 +50,7 @@ class FavouritesRepository @Inject constructor(
return entities.toMangaList()
}
fun observeAll(categoryId: Long, order: SortOrder): Flow<List<Manga>> {
fun observeAll(categoryId: Long, order: ListSortOrder): Flow<List<Manga>> {
return db.favouritesDao.observeAll(categoryId, order)
.mapItems { it.toManga() }
}
@@ -105,7 +104,7 @@ class FavouritesRepository @Inject constructor(
suspend fun createCategory(
title: String,
sortOrder: SortOrder,
sortOrder: ListSortOrder,
isTrackerEnabled: Boolean,
isVisibleOnShelf: Boolean,
): FavouriteCategory {
@@ -128,7 +127,7 @@ class FavouritesRepository @Inject constructor(
suspend fun updateCategory(
id: Long,
title: String,
sortOrder: SortOrder,
sortOrder: ListSortOrder,
isTrackerEnabled: Boolean,
isVisibleOnShelf: Boolean,
) {
@@ -156,7 +155,7 @@ class FavouritesRepository @Inject constructor(
}
}
suspend fun setCategoryOrder(id: Long, order: SortOrder) {
suspend fun setCategoryOrder(id: Long, order: ListSortOrder) {
db.favouriteCategoriesDao.updateOrder(id, order.name)
}
@@ -205,10 +204,10 @@ class FavouritesRepository @Inject constructor(
return ReversibleHandle { recoverToCategory(categoryId, ids) }
}
private fun observeOrder(categoryId: Long): Flow<SortOrder> {
private fun observeOrder(categoryId: Long): Flow<ListSortOrder> {
return db.favouriteCategoriesDao.observe(categoryId)
.filterNotNull()
.map { x -> SortOrder(x.order, SortOrder.NEWEST) }
.map { x -> ListSortOrder(x.order, ListSortOrder.NEWEST) }
.distinctUntilChanged()
}

View File

@@ -27,7 +27,6 @@ import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEdit
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.SortOrder
import javax.inject.Inject
@AndroidEntryPoint
@@ -176,12 +175,6 @@ class FavouriteCategoriesActivity :
companion object {
val SORT_ORDERS = arrayOf(
SortOrder.ALPHABETICAL,
SortOrder.NEWEST,
SortOrder.RATING,
)
fun newIntent(context: Context) = Intent(context, FavouriteCategoriesActivity::class.java)
}
}

View File

@@ -18,16 +18,15 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.model.titleRes
import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.getSerializableCompat
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.setChecked
import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal
import org.koitharu.kotatsu.databinding.ActivityCategoryEditBinding
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.list.domain.ListSortOrder
import com.google.android.material.R as materialR
@AndroidEntryPoint
@@ -38,7 +37,8 @@ class FavouritesCategoryEditActivity :
DefaultTextWatcher {
private val viewModel by viewModels<FavouritesCategoryEditViewModel>()
private var selectedSortOrder: SortOrder? = null
private var selectedSortOrder: ListSortOrder? = null
private val sortOrders = ListSortOrder.FAVORITES.sortedByOrdinal()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -68,7 +68,7 @@ class FavouritesCategoryEditActivity :
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
val order = savedInstanceState.getSerializableCompat<SortOrder>(KEY_SORT_ORDER)
val order = savedInstanceState.getSerializableCompat<ListSortOrder>(KEY_SORT_ORDER)
if (order != null) {
selectedSortOrder = order
}
@@ -103,7 +103,7 @@ class FavouritesCategoryEditActivity :
}
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedSortOrder = FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(position)
selectedSortOrder = sortOrders.getOrNull(position)
}
private fun onCategoryChanged(category: FavouriteCategory?) {
@@ -113,7 +113,7 @@ class FavouritesCategoryEditActivity :
}
viewBinding.editName.setText(category?.title)
selectedSortOrder = category?.order
val sortText = getString((category?.order ?: SortOrder.NEWEST).titleRes)
val sortText = getString((category?.order ?: ListSortOrder.NEWEST).titleResId)
viewBinding.editSort.setText(sortText, false)
viewBinding.switchTracker.setChecked(category?.isTrackingEnabled ?: true, false)
viewBinding.switchShelf.setChecked(category?.isVisibleInLibrary ?: true, false)
@@ -135,17 +135,17 @@ class FavouritesCategoryEditActivity :
}
private fun initSortSpinner() {
val entries = FavouriteCategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) }
val entries = sortOrders.map { getString(it.titleResId) }
val adapter = SortAdapter(this, entries)
viewBinding.editSort.setAdapter(adapter)
viewBinding.editSort.onItemClickListener = this
}
private fun getSelectedSortOrder(): SortOrder {
private fun getSelectedSortOrder(): ListSortOrder {
selectedSortOrder?.let { return it }
val entries = FavouriteCategoriesActivity.SORT_ORDERS.map { getString(it.titleRes) }
val entries = sortOrders.map { getString(it.titleResId) }
val index = entries.indexOf(viewBinding.editSort.text.toString())
return FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(index) ?: SortOrder.NEWEST
return sortOrders.getOrNull(index) ?: ListSortOrder.NEWEST
}
private class SortAdapter(

View File

@@ -17,7 +17,7 @@ import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity.Companion.EXTRA_ID
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity.Companion.NO_ID
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.list.domain.ListSortOrder
import javax.inject.Inject
@HiltViewModel
@@ -48,7 +48,7 @@ class FavouritesCategoryEditViewModel @Inject constructor(
fun save(
title: String,
sortOrder: SortOrder,
sortOrder: ListSortOrder,
isTrackerEnabled: Boolean,
isVisibleOnShelf: Boolean,
) {

View File

@@ -10,13 +10,11 @@ import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.model.titleRes
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -27,12 +25,14 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
override val isSwipeRefreshEnabled = false
val categoryId
get() = viewModel.categoryId
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
if (viewModel.categoryId != NO_ID) {
addMenuProvider(FavouritesListMenuProvider(binding.root.context, viewModel))
}
viewModel.sortOrder.observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))
}
override fun onScrolledToEnd() = Unit
@@ -40,14 +40,15 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
override fun onFilterClick(view: View?) {
val menu = PopupMenu(view?.context ?: return, view)
menu.setOnMenuItemClickListener(this)
for ((i, item) in FavouriteCategoriesActivity.SORT_ORDERS.withIndex()) {
menu.menu.add(Menu.NONE, Menu.NONE, i, item.titleRes)
val orders = ListSortOrder.FAVORITES.sortedByOrdinal()
for ((i, item) in orders.withIndex()) {
menu.menu.add(Menu.NONE, Menu.NONE, i, item.titleResId)
}
menu.show()
}
override fun onMenuItemClick(item: MenuItem): Boolean {
val order = FavouriteCategoriesActivity.SORT_ORDERS.getOrNull(item.order) ?: return false
val order = ListSortOrder.FAVORITES.sortedByOrdinal().getOrNull(item.order) ?: return false
viewModel.setSortOrder(order)
return true
}

View File

@@ -5,12 +5,8 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuProvider
import androidx.core.view.forEach
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.model.titleRes
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.parsers.model.SortOrder
class FavouritesListMenuProvider(
private val context: Context,
@@ -19,29 +15,9 @@ class FavouritesListMenuProvider(
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_favourites, menu)
val subMenu = menu.findItem(R.id.action_order)?.subMenu ?: return
for (order in FavouriteCategoriesActivity.SORT_ORDERS) {
subMenu.add(R.id.group_order, Menu.NONE, order.ordinal, order.titleRes)
}
subMenu.setGroupCheckable(R.id.group_order, true, true)
}
override fun onPrepareMenu(menu: Menu) {
super.onPrepareMenu(menu)
val order = viewModel.sortOrder.value ?: return
menu.findItem(R.id.action_order)?.subMenu?.forEach { item ->
if (item.order == order.ordinal) {
item.isChecked = true
}
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
if (menuItem.groupId == R.id.group_order) {
val order = SortOrder.entries[menuItem.order]
viewModel.setSortOrder(order)
return true
}
return when (menuItem.itemId) {
R.id.action_edit -> {
context.startActivity(FavouritesCategoryEditActivity.newIntent(context, viewModel.categoryId))

View File

@@ -22,12 +22,12 @@ import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.ARG_CATEGORY_ID
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.model.SortOrder
import javax.inject.Inject
@HiltViewModel
@@ -44,7 +44,7 @@ class FavouritesListViewModel @Inject constructor(
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_FAVORITES) { favoritesListMode }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.favoritesListMode)
val sortOrder: StateFlow<SortOrder?> = if (categoryId == NO_ID) {
val sortOrder: StateFlow<ListSortOrder?> = if (categoryId == NO_ID) {
MutableStateFlow(null)
} else {
repository.observeCategory(categoryId)
@@ -54,7 +54,7 @@ class FavouritesListViewModel @Inject constructor(
override val content = combine(
if (categoryId == NO_ID) {
repository.observeAll(SortOrder.NEWEST)
repository.observeAll(ListSortOrder.NEWEST)
} else {
repository.observeAll(categoryId)
},
@@ -98,7 +98,7 @@ class FavouritesListViewModel @Inject constructor(
}
}
fun setSortOrder(order: SortOrder) {
fun setSortOrder(order: ListSortOrder) {
if (categoryId == NO_ID) {
return
}

View File

@@ -25,6 +25,7 @@ import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.lifecycleScope
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel
import org.koitharu.kotatsu.filter.ui.model.FilterItem
import org.koitharu.kotatsu.filter.ui.model.FilterState
@@ -207,7 +208,7 @@ class FilterCoordinator @Inject constructor(
state: FilterState,
query: String,
): List<ListModel> {
val sortOrders = repository.sortOrders.sortedBy { it.ordinal }
val sortOrders = repository.sortOrders.sortedByOrdinal()
val tags = mergeTags(state.tags, allTags.tags).toList()
val list = ArrayList<ListModel>(tags.size + sortOrders.size + 3)
if (query.isEmpty()) {

View File

@@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.Flow
import org.intellij.lang.annotations.Language
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.history.domain.model.HistoryOrder
import org.koitharu.kotatsu.list.domain.ListSortOrder
@Dao
abstract class HistoryDao {
@@ -33,12 +33,13 @@ abstract class HistoryDao {
@Query("SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC LIMIT :limit")
abstract fun observeAll(limit: Int): Flow<List<HistoryWithManga>>
fun observeAll(order: HistoryOrder): Flow<List<HistoryWithManga>> {
fun observeAll(order: ListSortOrder): Flow<List<HistoryWithManga>> {
val orderBy = when (order) {
HistoryOrder.UPDATED -> "history.updated_at DESC"
HistoryOrder.CREATED -> "history.created_at DESC"
HistoryOrder.PROGRESS -> "history.percent DESC"
HistoryOrder.ALPHABETIC -> "manga.title"
ListSortOrder.UPDATED -> "history.updated_at DESC"
ListSortOrder.NEWEST -> "history.created_at DESC"
ListSortOrder.PROGRESS -> "history.percent DESC"
ListSortOrder.ALPHABETIC -> "manga.title"
else -> throw IllegalArgumentException("Sort order $order is not supported")
}
@Language("RoomSql")

View File

@@ -18,8 +18,8 @@ import org.koitharu.kotatsu.core.model.findById
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
import org.koitharu.kotatsu.core.util.ext.mapItems
import org.koitharu.kotatsu.history.domain.model.HistoryOrder
import org.koitharu.kotatsu.history.domain.model.MangaWithHistory
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
@@ -66,7 +66,7 @@ class HistoryRepository @Inject constructor(
}
}
fun observeAllWithHistory(order: HistoryOrder): Flow<List<MangaWithHistory>> {
fun observeAllWithHistory(order: ListSortOrder): Flow<List<MangaWithHistory>> {
return db.historyDao.observeAll(order).mapItems {
MangaWithHistory(
it.manga.toManga(it.tags.toMangaTags()),

View File

@@ -1,16 +0,0 @@
package org.koitharu.kotatsu.history.domain.model
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
enum class HistoryOrder(
@StringRes val titleResId: Int,
) {
UPDATED(R.string.updated),
CREATED(R.string.order_added),
PROGRESS(R.string.progress),
ALPHABETIC(R.string.by_name);
fun isGroupingSupported() = this == UPDATED || this == CREATED || this == PROGRESS
}

View File

@@ -26,9 +26,9 @@ import org.koitharu.kotatsu.core.util.ext.daysDiff
import org.koitharu.kotatsu.core.util.ext.onFirst
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.history.domain.model.HistoryOrder
import org.koitharu.kotatsu.history.domain.model.MangaWithHistory
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyHint
import org.koitharu.kotatsu.list.ui.model.EmptyState
@@ -54,7 +54,7 @@ class HistoryListViewModel @Inject constructor(
downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler) {
private val sortOrder: StateFlow<HistoryOrder> = settings.observeAsStateFlow(
private val sortOrder: StateFlow<ListSortOrder> = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.IO,
key = AppSettings.KEY_HISTORY_ORDER,
valueProducer = { historySortOrder },
@@ -123,10 +123,6 @@ class HistoryListViewModel @Inject constructor(
}
}
fun setGrouping(isGroupingEnabled: Boolean) {
settings.isHistoryGroupingEnabled = isGroupingEnabled
}
private suspend fun mapList(
list: List<MangaWithHistory>,
grouped: Boolean,
@@ -168,10 +164,10 @@ class HistoryListViewModel @Inject constructor(
return result
}
private fun MangaHistory.header(order: HistoryOrder): ListHeader? = when (order) {
HistoryOrder.UPDATED -> ListHeader(timeAgo(updatedAt))
HistoryOrder.CREATED -> ListHeader(timeAgo(createdAt))
HistoryOrder.PROGRESS -> ListHeader(
private fun MangaHistory.header(order: ListSortOrder): ListHeader? = when (order) {
ListSortOrder.UPDATED -> ListHeader(timeAgo(updatedAt))
ListSortOrder.NEWEST -> ListHeader(timeAgo(createdAt))
ListSortOrder.PROGRESS -> ListHeader(
when (percent) {
1f -> R.string.status_completed
in 0f..0.01f -> R.string.status_planned
@@ -180,7 +176,9 @@ class HistoryListViewModel @Inject constructor(
},
)
HistoryOrder.ALPHABETIC -> null
ListSortOrder.ALPHABETIC,
ListSortOrder.RELEVANCE,
ListSortOrder.RATING -> null
}
private fun timeAgo(date: Date): DateTimeAgo {

View File

@@ -0,0 +1,30 @@
package org.koitharu.kotatsu.list.domain
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.util.find
import java.util.EnumSet
enum class ListSortOrder(
@StringRes val titleResId: Int,
) {
UPDATED(R.string.updated),
NEWEST(R.string.order_added),
PROGRESS(R.string.progress),
ALPHABETIC(R.string.by_name),
RATING(R.string.by_rating),
RELEVANCE(R.string.by_relevance),
;
fun isGroupingSupported() = this == UPDATED || this == NEWEST || this == PROGRESS
companion object {
val HISTORY = EnumSet.of(UPDATED, NEWEST, PROGRESS, ALPHABETIC)
val FAVORITES = EnumSet.of(ALPHABETIC, NEWEST, RATING)
val SUGGESTIONS = EnumSet.of(RELEVANCE)
operator fun invoke(value: String, fallback: ListSortOrder) = entries.find(value) ?: fallback
}
}

View File

@@ -6,7 +6,11 @@ import android.view.MenuItem
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
import org.koitharu.kotatsu.history.ui.HistoryListFragment
import org.koitharu.kotatsu.list.ui.config.ListConfigBottomSheet
import org.koitharu.kotatsu.list.ui.config.ListConfigSection
import org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment
class MangaListMenuProvider(
private val fragment: Fragment,
@@ -18,7 +22,13 @@ class MangaListMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_list_mode -> {
ListConfigBottomSheet.show(fragment.childFragmentManager)
val section: ListConfigSection = when (fragment) {
is HistoryListFragment -> ListConfigSection.History
is SuggestionsFragment -> ListConfigSection.Suggestions
is FavouritesListFragment -> ListConfigSection.Favorites(fragment.categoryId)
else -> ListConfigSection.General
}
ListConfigBottomSheet.show(fragment.childFragmentManager, section)
true
}

View File

@@ -9,6 +9,7 @@ import android.widget.ArrayAdapter
import android.widget.CompoundButton
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.slider.Slider
import dagger.hilt.android.AndroidEntryPoint
@@ -18,11 +19,9 @@ import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter
import org.koitharu.kotatsu.databinding.SheetListModeBinding
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
import org.koitharu.kotatsu.history.domain.model.HistoryOrder
import org.koitharu.kotatsu.history.ui.HistoryListFragment
import javax.inject.Inject
@AndroidEntryPoint
@@ -33,8 +32,11 @@ class ListConfigBottomSheet :
AdapterView.OnItemSelectedListener {
@Inject
@Deprecated("")
lateinit var settings: AppSettings
private val viewModel by viewModels<ListConfigViewModel>()
override fun onCreateViewBinding(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -42,12 +44,7 @@ class ListConfigBottomSheet :
override fun onViewBindingCreated(binding: SheetListModeBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
val section = getSection()
val mode = when (section) {
Section.GENERAL -> settings.listMode
Section.HISTORY -> settings.historyListMode
Section.FAVORITES -> settings.favoritesListMode
}
val mode = viewModel.listMode
binding.buttonList.isChecked = mode == ListMode.LIST
binding.buttonListDetailed.isChecked = mode == ListMode.DETAILED_LIST
binding.buttonGrid.isChecked = mode == ListMode.GRID
@@ -55,27 +52,31 @@ class ListConfigBottomSheet :
binding.sliderGrid.isVisible = mode == ListMode.GRID
binding.sliderGrid.setLabelFormatter(IntPercentLabelFormatter(binding.root.context))
binding.sliderGrid.setValueRounded(settings.gridSize.toFloat())
binding.sliderGrid.setValueRounded(viewModel.gridSize.toFloat())
binding.sliderGrid.addOnChangeListener(this)
binding.checkableGroup.addOnButtonCheckedListener(this)
binding.switchGrouping.isVisible = section == Section.HISTORY
if (section == Section.HISTORY) {
binding.switchGrouping.isVisible = viewModel.isGroupingAvailable
if (viewModel.isGroupingAvailable) {
binding.switchGrouping.isEnabled = settings.historySortOrder.isGroupingSupported()
}
binding.switchGrouping.isChecked = settings.isHistoryGroupingEnabled
binding.switchGrouping.setOnCheckedChangeListener(this)
if (section == Section.HISTORY) {
val sortOrders = viewModel.getSortOrders()
if (sortOrders != null) {
binding.textViewOrderTitle.isVisible = true
binding.spinnerOrder.adapter = ArrayAdapter(
binding.spinnerOrder.context,
android.R.layout.simple_spinner_dropdown_item,
android.R.id.text1,
HistoryOrder.entries.map { getString(it.titleResId) },
sortOrders.map { binding.spinnerOrder.context.getString(it.titleResId) },
)
binding.spinnerOrder.setSelection(settings.historySortOrder.ordinal, false)
val selected = sortOrders.indexOf(viewModel.getSelectedSortOrder())
if (selected >= 0) {
binding.spinnerOrder.setSelection(selected, false)
}
binding.spinnerOrder.onItemSelectedListener = this
binding.cardOrder.isVisible = true
}
@@ -93,11 +94,7 @@ class ListConfigBottomSheet :
}
requireViewBinding().textViewGridTitle.isVisible = mode == ListMode.GRID
requireViewBinding().sliderGrid.isVisible = mode == ListMode.GRID
when (getSection()) {
Section.GENERAL -> settings.listMode = mode
Section.HISTORY -> settings.historyListMode = mode
Section.FAVORITES -> settings.favoritesListMode = mode
}
viewModel.listMode = mode
}
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
@@ -108,36 +105,28 @@ class ListConfigBottomSheet :
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
if (fromUser) {
settings.gridSize = value.toInt()
viewModel.gridSize = value.toInt()
}
}
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
when (parent.id) {
R.id.spinner_order -> {
val value = HistoryOrder.entries[position]
settings.historySortOrder = value
viewBinding?.switchGrouping?.isEnabled = value.isGroupingSupported()
viewModel.setSortOrder(position)
viewBinding?.switchGrouping?.isEnabled = settings.historySortOrder.isGroupingSupported()
}
}
}
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
private fun getSection(): Section = when (parentFragment) {
is HistoryListFragment -> Section.HISTORY
is FavouritesListFragment -> Section.FAVORITES
else -> Section.GENERAL
}
enum class Section {
GENERAL, HISTORY, FAVORITES;
}
companion object {
private const val TAG = "ListModeSelectDialog"
const val ARG_SECTION = "section"
fun show(fm: FragmentManager) = ListConfigBottomSheet().showDistinct(fm, TAG)
fun show(fm: FragmentManager, section: ListConfigSection) = ListConfigBottomSheet().withArgs(1) {
putParcelable(ARG_SECTION, section)
}.showDistinct(fm, TAG)
}
}

View File

@@ -0,0 +1,21 @@
package org.koitharu.kotatsu.list.ui.config
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
sealed interface ListConfigSection : Parcelable {
@Parcelize
data object History : ListConfigSection
@Parcelize
data object General : ListConfigSection
@Parcelize
data class Favorites(
val categoryId: Long,
) : ListConfigSection
@Parcelize
data object Suggestions : ListConfigSection
}

View File

@@ -0,0 +1,76 @@
package org.koitharu.kotatsu.list.ui.config
import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.list.domain.ListSortOrder
import javax.inject.Inject
@HiltViewModel
class ListConfigViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val settings: AppSettings,
private val favouritesRepository: FavouritesRepository,
) : BaseViewModel() {
val section = savedStateHandle.require<ListConfigSection>(ListConfigBottomSheet.ARG_SECTION)
var listMode: ListMode
get() = when (section) {
is ListConfigSection.Favorites -> settings.favoritesListMode
ListConfigSection.General -> settings.listMode
ListConfigSection.History -> settings.historyListMode
ListConfigSection.Suggestions -> settings.suggestionsListMode
}
set(value) {
when (section) {
is ListConfigSection.Favorites -> settings.favoritesListMode = value
ListConfigSection.General -> settings.listMode = value
ListConfigSection.History -> settings.historyListMode = value
ListConfigSection.Suggestions -> settings.suggestionsListMode = value
}
}
var gridSize: Int
get() = settings.gridSize
set(value) {
settings.gridSize = value
}
val isGroupingAvailable: Boolean
get() = section == ListConfigSection.History
fun getSortOrders(): List<ListSortOrder>? = when (section) {
is ListConfigSection.Favorites -> ListSortOrder.FAVORITES
ListConfigSection.General -> null
ListConfigSection.History -> ListSortOrder.HISTORY
ListConfigSection.Suggestions -> ListSortOrder.SUGGESTIONS
}?.sortedByOrdinal()
fun getSelectedSortOrder(): ListSortOrder? = when (section) {
is ListConfigSection.Favorites -> runBlocking { favouritesRepository.getCategory(section.categoryId).order }
ListConfigSection.General -> null
ListConfigSection.History -> settings.historySortOrder
ListConfigSection.Suggestions -> ListSortOrder.RELEVANCE // TODO
}
fun setSortOrder(position: Int) {
val value = getSortOrders()?.getOrNull(position) ?: return
when (section) {
is ListConfigSection.Favorites -> launchJob {
favouritesRepository.setCategoryOrder(section.categoryId, value)
}
ListConfigSection.General -> Unit
ListConfigSection.History -> settings.historySortOrder = value
ListConfigSection.Suggestions -> Unit
}
}
}

View File

@@ -7,7 +7,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.koitharu.kotatsu.core.model.findById
@@ -52,7 +52,7 @@ class PagesThumbnailsViewModel @Inject constructor(
init {
loadingJob = launchLoadingJob(Dispatchers.Default) {
chaptersLoader.init(checkNotNull(mangaDetails.last()))
chaptersLoader.init(checkNotNull(mangaDetails.first { x -> x?.isLoaded == true }))
chaptersLoader.loadSingleChapter(initialChapterId)
updateList()
}

View File

@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.util.ext.onFirst
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.domain.ListExtraProvider
@@ -31,6 +32,9 @@ class SuggestionsViewModel @Inject constructor(
private val suggestionsScheduler: SuggestionsWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler) {
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_SUGGESTIONS) { suggestionsListMode }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.suggestionsListMode)
override val content = combine(
repository.observeAll(),
listMode,

View File

@@ -2,22 +2,9 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_order"
android:orderInCategory="50"
android:title="@string/sort_order">
<menu>
<group android:id="@+id/group_order" />
</menu>
</item>
<item
android:id="@+id/action_edit"
android:orderInCategory="50"
android:orderInCategory="20"
android:title="@string/edit_category"
android:titleCondensed="@string/edit" />

View File

@@ -5,7 +5,7 @@
<item
android:id="@+id/action_manage"
android:orderInCategory="48"
android:title="@string/manage_categories"
android:titleCondensed="@string/manage" />
android:title="@string/favourites_categories"
android:titleCondensed="@string/categories" />
</menu>

View File

@@ -5,7 +5,7 @@
<item
android:id="@+id/action_list_mode"
android:orderInCategory="20"
android:orderInCategory="40"
android:title="@string/list_options"
app:showAsAction="never" />

View File

@@ -497,4 +497,6 @@
<string name="suggest_new_sources">Suggest new sources after app update</string>
<string name="suggest_new_sources_summary">Prompt to enable newly added sources after updating the application</string>
<string name="list_options">List options</string>
<string name="by_relevance">Relevance</string>
<string name="categories">Categories</string>
</resources>