Add category filters to Upcoming calendar (#3607)

This commit is contained in:
MajorTanya
2026-08-04 12:30:23 +02:00
committed by GitHub
parent f7a1ecd251
commit a73271aefa
11 changed files with 299 additions and 25 deletions
@@ -1,12 +1,16 @@
package mihon.feature.upcoming
import androidx.compose.runtime.Immutable
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMapIndexedNotNull
import androidx.lifecycle.viewModelScope
import eu.kanade.core.util.insertSeparatorsReversed
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.YearMonth
@@ -14,27 +18,46 @@ import kotlinx.datetime.toLocalDateTime
import kotlinx.datetime.yearMonth
import mihon.core.viewmodel.StateViewModel
import mihon.domain.upcoming.interactor.GetUpcomingManga
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.upcoming.service.UpcomingPreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Clock
class UpcomingViewModel(
private val getUpcomingManga: GetUpcomingManga = Injekt.get(),
val getCategories: GetCategories = Injekt.get(),
val upcomingPreferences: UpcomingPreferences = Injekt.get(),
) : StateViewModel<UpcomingViewModel.State>(State()) {
val excludedCategories = upcomingPreferences.filterExcludedCategories
val includedCategories = upcomingPreferences.filterIncludedCategories
init {
viewModelScope.launch {
getUpcomingManga.subscribe().collectLatest {
mutableState.update { state ->
val upcomingItems = it.toUpcomingUIModels()
state.copy(
items = upcomingItems,
events = upcomingItems.toEvents(),
headerIndexes = upcomingItems.getHeaderIndexes(),
viewModelScope.launchIO {
getUpcomingItemPreferenceFlow()
.distinctUntilChanged()
.flatMapLatest {
getUpcomingManga.subscribe(
excludedCategories = it.filterExcludedCategories,
includedCategories = it.filterIncludedCategories,
)
.distinctUntilChanged()
}
.collectLatest {
mutableState.update { state ->
val upcomingItems = it.toUpcomingUIModels()
state.copy(
items = upcomingItems,
events = upcomingItems.toEvents(),
headerIndexes = upcomingItems.getHeaderIndexes(),
)
}
}
}
}
}
@@ -81,6 +104,44 @@ class UpcomingViewModel(
mutableState.update { it.copy(selectedYearMonth = yearMonth) }
}
private fun getUpcomingItemPreferenceFlow(): Flow<ItemPreferences> {
return combine(
upcomingPreferences.filterExcludedCategories.changes(),
upcomingPreferences.filterIncludedCategories.changes(),
) { excluded, included ->
ItemPreferences(
filterExcludedCategories = excluded,
filterIncludedCategories = included,
)
}
}
fun resetDialog() {
mutableState.update { it.copy(dialog = null) }
}
fun showFilterDialog() {
mutableState.update { it.copy(dialog = Dialog.FilterSheet) }
}
fun cycleCategory(category: Category) {
when (category.id) {
in includedCategories.get() -> {
includedCategories.getAndSet { it - category.id }
excludedCategories.getAndSet { it + category.id }
}
in excludedCategories.get() -> excludedCategories.getAndSet { it - category.id }
else -> includedCategories.getAndSet { it + category.id }
}
}
@Immutable
private data class ItemPreferences(
val filterExcludedCategories: List<Long>,
val filterIncludedCategories: List<Long>,
)
data class State(
val selectedYearMonth: YearMonth = Clock.System.now()
.toLocalDateTime(TimeZone.currentSystemDefault())
@@ -89,5 +150,11 @@ class UpcomingViewModel(
val items: List<UpcomingUIModel> = listOf(),
val events: Map<LocalDate, Int> = mapOf(),
val headerIndexes: Map<LocalDate, Int> = mapOf(),
val hasActiveFilters: Boolean = false,
val dialog: Dialog? = null,
)
sealed interface Dialog {
data object FilterSheet : Dialog
}
}