Quick filter for favorites
This commit is contained in:
@@ -17,6 +17,9 @@ class NetworkState(
|
||||
|
||||
private val callback = NetworkCallbackImpl()
|
||||
|
||||
override val value: Boolean
|
||||
get() = connectivityManager.isOnline(settings)
|
||||
|
||||
@Synchronized
|
||||
override fun onActive() {
|
||||
invalidate()
|
||||
|
||||
@@ -13,7 +13,7 @@ abstract class MediatorStateFlow<T>(initialValue: T) : StateFlow<T> {
|
||||
final override val replayCache: List<T>
|
||||
get() = delegate.replayCache
|
||||
|
||||
final override val value: T
|
||||
override val value: T
|
||||
get() = delegate.value
|
||||
|
||||
final override suspend fun collect(collector: FlowCollector<T>): Nothing {
|
||||
|
||||
@@ -11,7 +11,9 @@ 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.toEntity
|
||||
import org.koitharu.kotatsu.favourites.domain.model.Cover
|
||||
import org.koitharu.kotatsu.list.domain.ListFilterOption
|
||||
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
||||
|
||||
@Dao
|
||||
@@ -27,21 +29,11 @@ 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: ListSortOrder, limit: Int): Flow<List<FavouriteManga>> {
|
||||
val orderBy = getOrderBy(order)
|
||||
val query = buildString {
|
||||
append(
|
||||
"SELECT * FROM favourites LEFT JOIN manga ON favourites.manga_id = manga.manga_id " +
|
||||
"WHERE favourites.deleted_at = 0 GROUP BY favourites.manga_id ORDER BY ",
|
||||
)
|
||||
append(orderBy)
|
||||
if (limit > 0) {
|
||||
append(" LIMIT ")
|
||||
append(limit)
|
||||
}
|
||||
}
|
||||
return observeAllImpl(SimpleSQLiteQuery(query))
|
||||
}
|
||||
fun observeAll(
|
||||
order: ListSortOrder,
|
||||
filterOptions: Set<ListFilterOption>,
|
||||
limit: Int
|
||||
): Flow<List<FavouriteManga>> = observeAll(0L, order, filterOptions, limit)
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM favourites WHERE deleted_at = 0 ORDER BY created_at DESC LIMIT :limit OFFSET :offset")
|
||||
@@ -57,13 +49,37 @@ abstract class FavouritesDao {
|
||||
)
|
||||
abstract suspend fun findAll(categoryId: Long): List<FavouriteManga>
|
||||
|
||||
fun observeAll(categoryId: Long, order: ListSortOrder, limit: Int): Flow<List<FavouriteManga>> {
|
||||
fun observeAll(
|
||||
categoryId: Long,
|
||||
order: ListSortOrder,
|
||||
filterOptions: Set<ListFilterOption>,
|
||||
limit: Int
|
||||
): Flow<List<FavouriteManga>> {
|
||||
val orderBy = getOrderBy(order)
|
||||
val query = buildString {
|
||||
append(
|
||||
"SELECT * FROM favourites LEFT JOIN manga ON favourites.manga_id = manga.manga_id " +
|
||||
"WHERE category_id = ? AND deleted_at = 0 GROUP BY favourites.manga_id ORDER BY ",
|
||||
"WHERE deleted_at = 0",
|
||||
)
|
||||
if (categoryId != 0L) {
|
||||
append(" AND category_id = ")
|
||||
append(categoryId)
|
||||
}
|
||||
val groupedOptions = filterOptions.groupBy { it.groupKey }
|
||||
for ((_, group) in groupedOptions) {
|
||||
if (group.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
append(" AND ")
|
||||
if (group.size > 1) {
|
||||
group.joinTo(this, separator = " OR ", prefix = "(", postfix = ")") {
|
||||
it.getCondition()
|
||||
}
|
||||
} else {
|
||||
append(group.single().getCondition())
|
||||
}
|
||||
}
|
||||
append(" GROUP BY favourites.manga_id ORDER BY ")
|
||||
append(orderBy)
|
||||
if (limit > 0) {
|
||||
append(" LIMIT ")
|
||||
@@ -71,7 +87,7 @@ abstract class FavouritesDao {
|
||||
}
|
||||
}
|
||||
|
||||
return observeAllImpl(SimpleSQLiteQuery(query, arrayOf<Any>(categoryId)))
|
||||
return observeAllImpl(SimpleSQLiteQuery(query))
|
||||
}
|
||||
|
||||
suspend fun findCovers(categoryId: Long, order: ListSortOrder): List<Cover> {
|
||||
@@ -191,4 +207,11 @@ abstract class FavouritesDao {
|
||||
|
||||
else -> throw IllegalArgumentException("Sort order $sortOrder is not supported")
|
||||
}
|
||||
|
||||
private fun ListFilterOption.getCondition(): String = when (this) {
|
||||
ListFilterOption.Macro.COMPLETED -> "EXISTS(SELECT * FROM history WHERE history.manga_id = favourites.manga_id AND history.percent >= 0.9999)"
|
||||
ListFilterOption.Macro.NEW_CHAPTERS -> "(SELECT chapters_new FROM tracks WHERE tracks.manga_id = favourites.manga_id) > 0"
|
||||
is ListFilterOption.Tag -> "EXISTS(SELECT * FROM manga_tags WHERE favourites.manga_id = manga_tags.manga_id AND tag_id = ${tag.toEntity().id})"
|
||||
else -> throw IllegalArgumentException("Unsupported option $this")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.koitharu.kotatsu.favourites.domain
|
||||
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.list.domain.ListFilterOption
|
||||
import org.koitharu.kotatsu.list.domain.MangaListQuickFilter
|
||||
import javax.inject.Inject
|
||||
|
||||
class FavoritesListQuickFilter @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
private val repository: FavouritesRepository,
|
||||
networkState: NetworkState,
|
||||
) : MangaListQuickFilter() {
|
||||
|
||||
init {
|
||||
setFilterOption(ListFilterOption.Downloaded, !networkState.value)
|
||||
}
|
||||
|
||||
override suspend fun getAvailableFilterOptions(): List<ListFilterOption> = buildList {
|
||||
add(ListFilterOption.Downloaded)
|
||||
if (settings.isTrackerEnabled) {
|
||||
add(ListFilterOption.Macro.NEW_CHAPTERS)
|
||||
}
|
||||
add(ListFilterOption.Macro.COMPLETED)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ 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.ListFilterOption
|
||||
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import javax.inject.Inject
|
||||
@@ -38,8 +39,8 @@ class FavouritesRepository @Inject constructor(
|
||||
return entities.toMangaList()
|
||||
}
|
||||
|
||||
fun observeAll(order: ListSortOrder, limit: Int): Flow<List<Manga>> {
|
||||
return db.getFavouritesDao().observeAll(order, limit)
|
||||
fun observeAll(order: ListSortOrder, filterOptions: Set<ListFilterOption>, limit: Int): Flow<List<Manga>> {
|
||||
return db.getFavouritesDao().observeAll(order, filterOptions, limit)
|
||||
.mapItems { it.toManga() }
|
||||
}
|
||||
|
||||
@@ -48,14 +49,19 @@ class FavouritesRepository @Inject constructor(
|
||||
return entities.toMangaList()
|
||||
}
|
||||
|
||||
fun observeAll(categoryId: Long, order: ListSortOrder, limit: Int): Flow<List<Manga>> {
|
||||
return db.getFavouritesDao().observeAll(categoryId, order, limit)
|
||||
fun observeAll(
|
||||
categoryId: Long,
|
||||
order: ListSortOrder,
|
||||
filterOptions: Set<ListFilterOption>,
|
||||
limit: Int
|
||||
): Flow<List<Manga>> {
|
||||
return db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit)
|
||||
.mapItems { it.toManga() }
|
||||
}
|
||||
|
||||
fun observeAll(categoryId: Long, limit: Int): Flow<List<Manga>> {
|
||||
fun observeAll(categoryId: Long, filterOptions: Set<ListFilterOption>, limit: Int): Flow<List<Manga>> {
|
||||
return observeOrder(categoryId)
|
||||
.flatMapLatest { order -> observeAll(categoryId, order, limit) }
|
||||
.flatMapLatest { order -> observeAll(categoryId, order, filterOptions, limit) }
|
||||
}
|
||||
|
||||
fun observeMangaCount(): Flow<Int> {
|
||||
|
||||
@@ -35,6 +35,8 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
|
||||
|
||||
override fun onScrolledToEnd() = viewModel.requestMoreItems()
|
||||
|
||||
override fun onEmptyActionClick() = viewModel.clearFilter()
|
||||
|
||||
override fun onFilterClick(view: View?) {
|
||||
val menu = PopupMenu(view?.context ?: return, view)
|
||||
menu.setOnMenuItemClickListener(this)
|
||||
|
||||
@@ -4,6 +4,9 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -15,21 +18,28 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.isLocal
|
||||
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.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||
import org.koitharu.kotatsu.favourites.domain.FavoritesListQuickFilter
|
||||
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.history.domain.MarkAsReadUseCase
|
||||
import org.koitharu.kotatsu.list.domain.ListFilterOption
|
||||
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
||||
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
||||
import org.koitharu.kotatsu.list.domain.QuickFilterListener
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
@@ -42,9 +52,11 @@ class FavouritesListViewModel @Inject constructor(
|
||||
private val repository: FavouritesRepository,
|
||||
private val mangaListMapper: MangaListMapper,
|
||||
private val markAsReadUseCase: MarkAsReadUseCase,
|
||||
private val quickFilter: FavoritesListQuickFilter,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
settings: AppSettings,
|
||||
downloadScheduler: DownloadWorker.Scheduler,
|
||||
) : MangaListViewModel(settings, downloadScheduler) {
|
||||
) : MangaListViewModel(settings, downloadScheduler), QuickFilterListener by quickFilter {
|
||||
|
||||
val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID
|
||||
private val refreshTrigger = MutableStateFlow(Any())
|
||||
@@ -66,26 +78,19 @@ class FavouritesListViewModel @Inject constructor(
|
||||
|
||||
override val content = combine(
|
||||
observeFavorites(),
|
||||
quickFilter.appliedOptions,
|
||||
observeListModeWithTriggers(),
|
||||
refreshTrigger,
|
||||
) { list, mode, _ ->
|
||||
) { list, filters, mode, _ ->
|
||||
when {
|
||||
list.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_empty_favourites,
|
||||
textPrimary = R.string.text_empty_holder_primary,
|
||||
textSecondary = if (categoryId == NO_ID) {
|
||||
R.string.you_have_not_favourites_yet
|
||||
} else {
|
||||
R.string.favourites_category_empty
|
||||
},
|
||||
actionStringRes = 0,
|
||||
),
|
||||
)
|
||||
list.isEmpty() -> if (filters.isEmpty()) {
|
||||
listOf(getEmptyState(hasFilters = false))
|
||||
} else {
|
||||
listOf(quickFilter.filterItem(filters), getEmptyState(hasFilters = true))
|
||||
}
|
||||
|
||||
else -> {
|
||||
isReady.set(true)
|
||||
mangaListMapper.toListModelList(list, mode)
|
||||
list.mapList(mode, filters).also { isReady.set(true) }
|
||||
}
|
||||
}
|
||||
}.catch {
|
||||
@@ -134,12 +139,55 @@ class FavouritesListViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeFavorites() = if (categoryId == NO_ID) {
|
||||
combine(sortOrder.filterNotNull(), limit, ::Pair)
|
||||
.flatMapLatest { repository.observeAll(it.first, it.second) }
|
||||
} else {
|
||||
limit.flatMapLatest {
|
||||
repository.observeAll(categoryId, it)
|
||||
private suspend fun List<Manga>.mapList(mode: ListMode, filters: Set<ListFilterOption>): List<ListModel> {
|
||||
val list = if (ListFilterOption.Downloaded in filters) {
|
||||
mapToLocal()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
val result = ArrayList<ListModel>(list.size + 1)
|
||||
result += quickFilter.filterItem(filters)
|
||||
mangaListMapper.toListModelList(result, list, mode)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun observeFavorites() = if (categoryId == NO_ID) {
|
||||
combine(sortOrder.filterNotNull(), quickFilter.appliedOptions, limit, ::Triple)
|
||||
.flatMapLatest { repository.observeAll(it.first, it.second - ListFilterOption.Downloaded, it.third) }
|
||||
} else {
|
||||
combine(quickFilter.appliedOptions, limit, ::Pair)
|
||||
.flatMapLatest { repository.observeAll(categoryId, it.first - ListFilterOption.Downloaded, it.second) }
|
||||
}
|
||||
|
||||
private fun getEmptyState(hasFilters: Boolean) = if (hasFilters) {
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_empty_favourites,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = R.string.text_empty_holder_secondary_filtered,
|
||||
actionStringRes = R.string.reset_filter,
|
||||
)
|
||||
} else {
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_empty_favourites,
|
||||
textPrimary = R.string.text_empty_holder_primary,
|
||||
textSecondary = if (categoryId == NO_ID) {
|
||||
R.string.you_have_not_favourites_yet
|
||||
} else {
|
||||
R.string.favourites_category_empty
|
||||
},
|
||||
actionStringRes = 0,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun List<Manga>.mapToLocal(): List<Manga> = coroutineScope {
|
||||
map {
|
||||
async {
|
||||
if (it.isLocal) {
|
||||
it
|
||||
} else {
|
||||
localMangaRepository.findSavedManga(it)?.manga
|
||||
}
|
||||
}
|
||||
}.awaitAll().filterNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.history.domain
|
||||
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.list.domain.ListFilterOption
|
||||
@@ -9,8 +10,13 @@ import javax.inject.Inject
|
||||
class HistoryListQuickFilter @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
private val repository: HistoryRepository,
|
||||
networkState: NetworkState,
|
||||
) : MangaListQuickFilter() {
|
||||
|
||||
init {
|
||||
setFilterOption(ListFilterOption.Downloaded, !networkState.value)
|
||||
}
|
||||
|
||||
override suspend fun getAvailableFilterOptions(): List<ListFilterOption> = buildList {
|
||||
add(ListFilterOption.Downloaded)
|
||||
if (settings.isTrackerEnabled) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.isLocal
|
||||
import org.koitharu.kotatsu.core.os.NetworkManageIntent
|
||||
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
||||
import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper
|
||||
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
|
||||
@@ -34,9 +33,7 @@ class HistoryListFragment : MangaListFragment() {
|
||||
|
||||
override fun onScrolledToEnd() = viewModel.requestMoreItems()
|
||||
|
||||
override fun onEmptyActionClick() {
|
||||
startActivity(NetworkManageIntent())
|
||||
}
|
||||
override fun onEmptyActionClick() = viewModel.clearFilter()
|
||||
|
||||
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_history, menu)
|
||||
|
||||
@@ -18,7 +18,6 @@ import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.MangaHistory
|
||||
import org.koitharu.kotatsu.core.model.isLocal
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
@@ -26,7 +25,6 @@ import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.combine
|
||||
import org.koitharu.kotatsu.core.util.ext.onFirst
|
||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
@@ -38,7 +36,6 @@ import org.koitharu.kotatsu.list.domain.ListSortOrder
|
||||
import org.koitharu.kotatsu.list.domain.MangaListMapper
|
||||
import org.koitharu.kotatsu.list.domain.QuickFilterListener
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyHint
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||
import org.koitharu.kotatsu.list.ui.model.ListHeader
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
@@ -61,7 +58,6 @@ class HistoryListViewModel @Inject constructor(
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val markAsReadUseCase: MarkAsReadUseCase,
|
||||
private val quickFilter: HistoryListQuickFilter,
|
||||
networkState: NetworkState,
|
||||
downloadScheduler: DownloadWorker.Scheduler,
|
||||
) : MangaListViewModel(settings, downloadScheduler), QuickFilterListener by quickFilter {
|
||||
|
||||
@@ -98,9 +94,8 @@ class HistoryListViewModel @Inject constructor(
|
||||
observeHistory(),
|
||||
isGroupingEnabled,
|
||||
observeListModeWithTriggers(),
|
||||
networkState,
|
||||
settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) { isIncognitoModeEnabled },
|
||||
) { filters, list, grouped, mode, online, incognito ->
|
||||
) { filters, list, grouped, mode, incognito ->
|
||||
when {
|
||||
list.isEmpty() -> {
|
||||
if (filters.isEmpty()) {
|
||||
@@ -112,7 +107,7 @@ class HistoryListViewModel @Inject constructor(
|
||||
|
||||
else -> {
|
||||
isReady.set(true)
|
||||
mapList(filters, list, grouped, mode, online, incognito)
|
||||
mapList(list, grouped, mode, filters, incognito)
|
||||
}
|
||||
}
|
||||
}.onStart {
|
||||
@@ -166,19 +161,18 @@ class HistoryListViewModel @Inject constructor(
|
||||
.flatMapLatest { repository.observeAllWithHistory(it.first, it.second - ListFilterOption.Downloaded, it.third) }
|
||||
|
||||
private suspend fun mapList(
|
||||
filters: Set<ListFilterOption>,
|
||||
historyList: List<MangaWithHistory>,
|
||||
grouped: Boolean,
|
||||
mode: ListMode,
|
||||
isOnline: Boolean,
|
||||
filters: Set<ListFilterOption>,
|
||||
isIncognito: Boolean,
|
||||
): List<ListModel> {
|
||||
val list = if (!isOnline || ListFilterOption.Downloaded in filters) {
|
||||
val list = if (ListFilterOption.Downloaded in filters) {
|
||||
historyList.mapToLocal()
|
||||
} else {
|
||||
historyList
|
||||
}
|
||||
val result = ArrayList<ListModel>((if (grouped) (list.size * 1.4).toInt() else list.size) + 3)
|
||||
val result = ArrayList<ListModel>((if (grouped) (list.size * 1.4).toInt() else list.size) + 2)
|
||||
result += quickFilter.filterItem(filters)
|
||||
if (isIncognito) {
|
||||
result += TipModel(
|
||||
@@ -192,14 +186,6 @@ class HistoryListViewModel @Inject constructor(
|
||||
}
|
||||
val order = sortOrder.value
|
||||
var prevHeader: ListHeader? = null
|
||||
if (!isOnline) {
|
||||
result += EmptyHint(
|
||||
icon = R.drawable.ic_empty_common,
|
||||
textPrimary = R.string.network_unavailable,
|
||||
textSecondary = R.string.network_unavailable_hint,
|
||||
actionStringRes = R.string.manage,
|
||||
)
|
||||
}
|
||||
var isEmpty = true
|
||||
for ((manga, history) in list) {
|
||||
isEmpty = false
|
||||
@@ -263,8 +249,8 @@ class HistoryListViewModel @Inject constructor(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_empty_history,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = R.string.text_history_holder_secondary_filtered,
|
||||
actionStringRes = 0,
|
||||
textSecondary = R.string.text_empty_holder_secondary_filtered,
|
||||
actionStringRes = R.string.reset_filter,
|
||||
)
|
||||
} else {
|
||||
EmptyState(
|
||||
|
||||
@@ -17,6 +17,16 @@ abstract class MangaListQuickFilter : QuickFilterListener {
|
||||
val appliedOptions
|
||||
get() = appliedFilter.asStateFlow()
|
||||
|
||||
override fun setFilterOption(option: ListFilterOption, isApplied: Boolean) {
|
||||
appliedFilter.value = ArraySet(appliedFilter.value).also {
|
||||
if (isApplied) {
|
||||
it.add(option)
|
||||
} else {
|
||||
it.remove(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toggleFilterOption(option: ListFilterOption) {
|
||||
appliedFilter.value = ArraySet(appliedFilter.value).also {
|
||||
if (option in it) {
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.koitharu.kotatsu.list.domain
|
||||
|
||||
interface QuickFilterListener {
|
||||
|
||||
fun setFilterOption(option: ListFilterOption, isApplied: Boolean)
|
||||
|
||||
fun toggleFilterOption(option: ListFilterOption)
|
||||
|
||||
fun clearFilter()
|
||||
|
||||
@@ -17,3 +17,12 @@ fun Throwable.toErrorState(canRetry: Boolean = true, @StringRes secondaryAction:
|
||||
fun Throwable.toErrorFooter() = ErrorFooter(
|
||||
exception = this,
|
||||
)
|
||||
|
||||
operator fun ListModel.plus(list: List<ListModel>): List<ListModel> {
|
||||
val result = ArrayList<ListModel>(list.size + 1)
|
||||
result.add(this)
|
||||
result.addAll(list)
|
||||
return result
|
||||
}
|
||||
|
||||
operator fun ListModel.plus(other: ListModel): List<ListModel> = listOf(this, other)
|
||||
@@ -5,6 +5,7 @@
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="@dimen/list_spacing"
|
||||
android:scrollbars="none">
|
||||
@@ -15,7 +16,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingVertical="2dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="6dp"
|
||||
app:chipStyle="@style/Widget.Kotatsu.Chip.Filter"
|
||||
app:selectionRequired="false"
|
||||
app:singleLine="true"
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<string name="text_search_holder_secondary">Try to reformulate the query.</string>
|
||||
<string name="text_history_holder_primary">What you read will be displayed here</string>
|
||||
<string name="text_history_holder_secondary">Find what to read in the «Explore» section</string>
|
||||
<string name="text_history_holder_secondary_filtered">There are no manga matching the filters you selected</string>
|
||||
<string name="text_empty_holder_secondary_filtered">There are no manga matching the filters you selected</string>
|
||||
<string name="text_local_holder_primary">Save something first</string>
|
||||
<string name="text_local_holder_secondary">Save something from an online catalog or import it from a file.</string>
|
||||
<string name="manga_shelf">Shelf</string>
|
||||
|
||||
Reference in New Issue
Block a user