diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/os/NetworkState.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/NetworkState.kt index 338818b5c..564db96aa 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/os/NetworkState.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/NetworkState.kt @@ -17,6 +17,9 @@ class NetworkState( private val callback = NetworkCallbackImpl() + override val value: Boolean + get() = connectivityManager.isOnline(settings) + @Synchronized override fun onActive() { invalidate() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/MediatorStateFlow.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/MediatorStateFlow.kt index 7bee7ffc2..a8dfc3090 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/MediatorStateFlow.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/MediatorStateFlow.kt @@ -13,7 +13,7 @@ abstract class MediatorStateFlow(initialValue: T) : StateFlow { final override val replayCache: List get() = delegate.replayCache - final override val value: T + override val value: T get() = delegate.value final override suspend fun collect(collector: FlowCollector): Nothing { 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 982540f1d..5fd49e107 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 @@ -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 - fun observeAll(order: ListSortOrder, limit: Int): Flow> { - 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, + limit: Int + ): Flow> = 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 - fun observeAll(categoryId: Long, order: ListSortOrder, limit: Int): Flow> { + fun observeAll( + categoryId: Long, + order: ListSortOrder, + filterOptions: Set, + limit: Int + ): Flow> { 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(categoryId))) + return observeAllImpl(SimpleSQLiteQuery(query)) } suspend fun findCovers(categoryId: Long, order: ListSortOrder): List { @@ -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") + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavoritesListQuickFilter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavoritesListQuickFilter.kt new file mode 100644 index 000000000..8eddca094 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/domain/FavoritesListQuickFilter.kt @@ -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 = buildList { + add(ListFilterOption.Downloaded) + if (settings.isTrackerEnabled) { + add(ListFilterOption.Macro.NEW_CHAPTERS) + } + add(ListFilterOption.Macro.COMPLETED) + } +} 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 eecac0d30..03f0d2feb 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 @@ -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> { - return db.getFavouritesDao().observeAll(order, limit) + fun observeAll(order: ListSortOrder, filterOptions: Set, limit: Int): Flow> { + 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> { - return db.getFavouritesDao().observeAll(categoryId, order, limit) + fun observeAll( + categoryId: Long, + order: ListSortOrder, + filterOptions: Set, + limit: Int + ): Flow> { + return db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit) .mapItems { it.toManga() } } - fun observeAll(categoryId: Long, limit: Int): Flow> { + fun observeAll(categoryId: Long, filterOptions: Set, limit: Int): Flow> { return observeOrder(categoryId) - .flatMapLatest { order -> observeAll(categoryId, order, limit) } + .flatMapLatest { order -> observeAll(categoryId, order, filterOptions, limit) } } fun observeMangaCount(): Flow { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt index 0d2f78118..6a08dbcfa 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt @@ -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) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt index 249b70c47..4babfe2ee 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt @@ -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.mapList(mode: ListMode, filters: Set): List { + val list = if (ListFilterOption.Downloaded in filters) { + mapToLocal() + } else { + this } + val result = ArrayList(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.mapToLocal(): List = coroutineScope { + map { + async { + if (it.isLocal) { + it + } else { + localMangaRepository.findSavedManga(it)?.manga + } + } + }.awaitAll().filterNotNull() } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/domain/HistoryListQuickFilter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/domain/HistoryListQuickFilter.kt index 109293d19..6cea47efd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/domain/HistoryListQuickFilter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/domain/HistoryListQuickFilter.kt @@ -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 = buildList { add(ListFilterOption.Downloaded) if (settings.isTrackerEnabled) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt index 03c2ee975..f9f54e021 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt @@ -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) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt index c4d47d4ce..b50c472f2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt @@ -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, historyList: List, grouped: Boolean, mode: ListMode, - isOnline: Boolean, + filters: Set, isIncognito: Boolean, ): List { - val list = if (!isOnline || ListFilterOption.Downloaded in filters) { + val list = if (ListFilterOption.Downloaded in filters) { historyList.mapToLocal() } else { historyList } - val result = ArrayList((if (grouped) (list.size * 1.4).toInt() else list.size) + 3) + val result = ArrayList((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( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/MangaListQuickFilter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/MangaListQuickFilter.kt index 9ef277513..0a2f360fc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/MangaListQuickFilter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/MangaListQuickFilter.kt @@ -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) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/QuickFilterListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/QuickFilterListener.kt index d6f5fd5f6..9afba5a36 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/QuickFilterListener.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/QuickFilterListener.kt @@ -2,6 +2,8 @@ package org.koitharu.kotatsu.list.domain interface QuickFilterListener { + fun setFilterOption(option: ListFilterOption, isApplied: Boolean) + fun toggleFilterOption(option: ListFilterOption) fun clearFilter() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelExt.kt similarity index 71% rename from app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt rename to app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelExt.kt index d489538b2..e71253b0b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelConversionExt.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/ListModelExt.kt @@ -17,3 +17,12 @@ fun Throwable.toErrorState(canRetry: Boolean = true, @StringRes secondaryAction: fun Throwable.toErrorFooter() = ErrorFooter( exception = this, ) + +operator fun ListModel.plus(list: List): List { + val result = ArrayList(list.size + 1) + result.add(this) + result.addAll(list) + return result +} + +operator fun ListModel.plus(other: ListModel): List = listOf(this, other) diff --git a/app/src/main/res/layout/item_quick_filter.xml b/app/src/main/res/layout/item_quick_filter.xml index fa301a8d5..2866a3951 100644 --- a/app/src/main/res/layout/item_quick_filter.xml +++ b/app/src/main/res/layout/item_quick_filter.xml @@ -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" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c7b6b25a6..1ddbc9e5b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -96,7 +96,7 @@ Try to reformulate the query. What you read will be displayed here Find what to read in the «Explore» section - There are no manga matching the filters you selected + There are no manga matching the filters you selected Save something first Save something from an online catalog or import it from a file. Shelf