History sorting #428

This commit is contained in:
Koitharu
2023-07-25 15:36:50 +03:00
parent ac1a919476
commit 7c7106a63c
10 changed files with 137 additions and 18 deletions

View File

@@ -23,6 +23,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.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
import org.koitharu.kotatsu.parsers.util.mapToSet
@@ -272,6 +273,10 @@ 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)
set(value) = prefs.edit { putEnumValue(KEY_HISTORY_ORDER, value) }
val isWebtoonZoomEnable: Boolean
get() = prefs.getBoolean(KEY_WEBTOON_ZOOM, true)
@@ -418,6 +423,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_SHORTCUTS = "dynamic_shortcuts"
const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
const val KEY_LOCAL_LIST_ORDER = "local_order"
const val KEY_HISTORY_ORDER = "history_order"
const val KEY_WEBTOON_ZOOM = "webtoon_zoom"
const val KEY_PREFETCH_CONTENT = "prefetch_content"
const val KEY_APP_LOCALE = "app_locale"

View File

@@ -0,0 +1,13 @@
package org.koitharu.kotatsu.core.ui.util
import androidx.fragment.app.Fragment
import kotlinx.coroutines.flow.FlowCollector
class MenuInvalidator(
private val fragment: Fragment,
) : FlowCollector<Any> {
override suspend fun emit(value: Any) {
fragment.activity?.invalidateMenu()
}
}

View File

@@ -4,10 +4,15 @@ 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.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.core.db.entity.TagEntity
import org.koitharu.kotatsu.history.domain.model.HistoryOrder
@Dao
abstract class HistoryDao {
@@ -28,6 +33,22 @@ 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>> {
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"
}
@Language("RoomSql")
val query = SimpleSQLiteQuery(
"SELECT * FROM history LEFT JOIN manga ON history.manga_id = manga.manga_id " +
"WHERE history.deleted_at = 0 GROUP BY history.manga_id ORDER BY $orderBy",
)
return observeAllImpl(query)
}
@Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM history WHERE deleted_at = 0)")
abstract suspend fun findAllManga(): List<MangaEntity>
@@ -111,4 +132,8 @@ abstract class HistoryDao {
@Query("UPDATE history SET deleted_at = :deletedAt WHERE created_at >= :minDate AND deleted_at = 0")
protected abstract suspend fun setDeletedAtAfter(minDate: Long, deletedAt: Long)
@Transaction
@RawQuery(observedEntities = [HistoryEntity::class])
protected abstract fun observeAllImpl(query: SupportSQLiteQuery): Flow<List<HistoryWithManga>>
}

View File

@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.model.MangaHistory
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.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
@@ -64,8 +65,8 @@ class HistoryRepository @Inject constructor(
}
}
fun observeAllWithHistory(): Flow<List<MangaWithHistory>> {
return db.historyDao.observeAll().mapItems {
fun observeAllWithHistory(order: HistoryOrder): Flow<List<MangaWithHistory>> {
return db.historyDao.observeAll(order).mapItems {
MangaWithHistory(
it.manga.toManga(it.tags.toMangaTags()),
it.history.toMangaHistory(),

View File

@@ -0,0 +1,16 @@
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
}

View File

@@ -8,6 +8,7 @@ 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.util.MenuInvalidator
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.databinding.FragmentListBinding
@@ -23,9 +24,9 @@ class HistoryListFragment : MangaListFragment() {
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
addMenuProvider(HistoryListMenuProvider(binding.root.context, viewModel))
viewModel.isGroupingEnabled.observe(viewLifecycleOwner) {
activity?.invalidateOptionsMenu()
}
val menuInvalidator = MenuInvalidator(this)
viewModel.isGroupingEnabled.observe(viewLifecycleOwner, menuInvalidator)
viewModel.sortOrder.observe(viewLifecycleOwner, menuInvalidator)
}
override fun onScrolledToEnd() = Unit

View File

@@ -5,10 +5,12 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuProvider
import androidx.core.view.forEach
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.dialog.RememberSelectionDialogListener
import org.koitharu.kotatsu.core.util.ext.startOfDay
import org.koitharu.kotatsu.history.domain.model.HistoryOrder
import java.util.Date
import java.util.concurrent.TimeUnit
import com.google.android.material.R as materialR
@@ -20,24 +22,45 @@ class HistoryListMenuProvider(
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_history, menu)
val subMenu = menu.findItem(R.id.action_order)?.subMenu ?: return
for (order in HistoryOrder.values()) {
subMenu.add(R.id.group_order, Menu.NONE, order.ordinal, order.titleResId)
}
subMenu.setGroupCheckable(R.id.group_order, true, true)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_clear_history -> {
showClearHistoryDialog()
true
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
if (menuItem.groupId == R.id.group_order) {
val order = enumValues<HistoryOrder>()[menuItem.order]
viewModel.setSortOrder(order)
return true
}
return when (menuItem.itemId) {
R.id.action_clear_history -> {
showClearHistoryDialog()
true
}
R.id.action_history_grouping -> {
viewModel.setGrouping(!menuItem.isChecked)
true
R.id.action_history_grouping -> {
viewModel.setGrouping(!menuItem.isChecked)
true
}
else -> false
}
else -> false
}
override fun onPrepareMenu(menu: Menu) {
menu.findItem(R.id.action_history_grouping)?.isChecked = viewModel.isGroupingEnabled.value == true
val order = viewModel.sortOrder.value ?: return
menu.findItem(R.id.action_order)?.subMenu?.forEach { item ->
if (item.order == order.ordinal) {
item.isChecked = true
}
}
menu.findItem(R.id.action_history_grouping)?.run {
isChecked = viewModel.isGroupingEnabled.value == true
isEnabled = order.isGroupingSupported()
}
}
private fun showClearHistoryDialog() {

View File

@@ -4,14 +4,17 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.onStart
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.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
@@ -20,6 +23,7 @@ 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.ui.MangaListViewModel
@@ -43,14 +47,25 @@ class HistoryListViewModel @Inject constructor(
downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler) {
val isGroupingEnabled = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default,
val sortOrder: StateFlow<HistoryOrder> = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.IO,
key = AppSettings.KEY_HISTORY_ORDER,
valueProducer = { historySortOrder },
)
val isGroupingEnabled = settings.observeAsFlow(
key = AppSettings.KEY_HISTORY_GROUPING,
valueProducer = { isHistoryGroupingEnabled },
).combine(sortOrder) { g, s ->
g && s.isGroupingSupported()
}.stateIn(
scope = viewModelScope + Dispatchers.Default,
started = SharingStarted.Eagerly,
initialValue = settings.isHistoryGroupingEnabled && sortOrder.value.isGroupingSupported(),
)
override val content = combine(
repository.observeAllWithHistory(),
sortOrder.flatMapLatest { repository.observeAllWithHistory(it) },
isGroupingEnabled,
listMode,
) { list, grouped, mode ->
@@ -78,6 +93,10 @@ class HistoryListViewModel @Inject constructor(
override fun onRetry() = Unit
fun setSortOrder(order: HistoryOrder) {
settings.historySortOrder = order
}
fun clearHistory(minDate: Long) {
launchJob(Dispatchers.Default) {
val stringRes = if (minDate <= 0) {

View File

@@ -3,6 +3,19 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_order"
android:orderInCategory="25"
android:title="@string/sort_order">
<menu>
<group android:id="@+id/group_order" />
</menu>
</item>
<item
android:id="@+id/action_history_grouping"
android:checkable="true"

View File

@@ -464,4 +464,6 @@
<string name="suggestions_wifi_only_summary">Do not update suggestions using metered network connections</string>
<string name="tracker_wifi_only_summary">Do not check for new chapters using metered network connections</string>
<string name="search_hint">Enter manga title, genre or source name</string>
<string name="progress">Progress</string>
<string name="order_added">Added</string>
</resources>