From 87bdee59908c73f340c892e43b522727e07b33d2 Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 10 Jul 2023 17:25:52 -0400 Subject: [PATCH] Move SettingsItems composables to presentation-core --- .../eu/kanade/domain/manga/model/Manga.kt | 18 +-- .../presentation/components/SettingsItems.kt | 128 ------------------ .../library/LibrarySettingsDialog.kt | 6 +- .../manga/ChapterSettingsDialog.kt | 28 ++-- .../java/eu/kanade/tachiyomi/Migrations.kt | 11 +- .../source/browse/SourceFilterDialog.kt | 22 +-- .../ui/library/LibraryScreenModel.kt | 34 ++--- .../ui/library/LibrarySettingsScreenModel.kt | 4 +- .../tachiyomi/ui/manga/MangaScreenModel.kt | 26 ++-- .../tachiyomi/core/preference/TriState.kt | 16 +++ .../library/service/LibraryPreferences.kt | 24 ++-- .../tachiyomi/domain/manga/model/Manga.kt | 17 +-- .../tachiyomi/domain/manga/model/TriState.kt | 9 ++ .../domain/manga/model/TriStateFilter.kt | 22 --- presentation-core/build.gradle.kts | 2 + .../core/components/SettingsItems.kt | 121 ++++++++++++++++- 16 files changed, 238 insertions(+), 250 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/presentation/components/SettingsItems.kt create mode 100644 core/src/main/java/tachiyomi/core/preference/TriState.kt create mode 100644 domain/src/main/java/tachiyomi/domain/manga/model/TriState.kt delete mode 100644 domain/src/main/java/tachiyomi/domain/manga/model/TriStateFilter.kt diff --git a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt index 94c1353f97..ef8efa3591 100644 --- a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt @@ -7,9 +7,9 @@ import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import tachiyomi.core.metadata.comicinfo.ComicInfo import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus +import tachiyomi.core.preference.TriState import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.manga.model.Manga -import tachiyomi.domain.manga.model.TriStateFilter import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -20,19 +20,19 @@ val Manga.readingModeType: Long val Manga.orientationType: Long get() = viewerFlags and OrientationType.MASK.toLong() -val Manga.downloadedFilter: TriStateFilter +val Manga.downloadedFilter: TriState get() { - if (forceDownloaded()) return TriStateFilter.ENABLED_IS + if (forceDownloaded()) return TriState.ENABLED_IS return when (downloadedFilterRaw) { - Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS - Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT - else -> TriStateFilter.DISABLED + Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS + Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT + else -> TriState.DISABLED } } fun Manga.chaptersFiltered(): Boolean { - return unreadFilter != TriStateFilter.DISABLED || - downloadedFilter != TriStateFilter.DISABLED || - bookmarkedFilter != TriStateFilter.DISABLED + return unreadFilter != TriState.DISABLED || + downloadedFilter != TriState.DISABLED || + bookmarkedFilter != TriState.DISABLED } fun Manga.forceDownloaded(): Boolean { return favorite && Injekt.get().downloadedOnly().get() diff --git a/app/src/main/java/eu/kanade/presentation/components/SettingsItems.kt b/app/src/main/java/eu/kanade/presentation/components/SettingsItems.kt deleted file mode 100644 index 57549e9904..0000000000 --- a/app/src/main/java/eu/kanade/presentation/components/SettingsItems.kt +++ /dev/null @@ -1,128 +0,0 @@ -package eu.kanade.presentation.components - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.ContentAlpha -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.CheckBox -import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank -import androidx.compose.material.icons.rounded.DisabledByDefault -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import tachiyomi.domain.manga.model.TriStateFilter -import tachiyomi.presentation.core.components.SettingsItemsPaddings - -@Composable -fun TriStateItem( - label: String, - state: TriStateFilter, - enabled: Boolean = true, - onClick: ((TriStateFilter) -> Unit)?, -) { - Row( - modifier = Modifier - .clickable( - enabled = enabled && onClick != null, - onClick = { - when (state) { - TriStateFilter.DISABLED -> onClick?.invoke(TriStateFilter.ENABLED_IS) - TriStateFilter.ENABLED_IS -> onClick?.invoke(TriStateFilter.ENABLED_NOT) - TriStateFilter.ENABLED_NOT -> onClick?.invoke(TriStateFilter.DISABLED) - } - }, - ) - .fillMaxWidth() - .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(24.dp), - ) { - val stateAlpha = if (enabled && onClick != null) 1f else ContentAlpha.disabled - - Icon( - imageVector = when (state) { - TriStateFilter.DISABLED -> Icons.Rounded.CheckBoxOutlineBlank - TriStateFilter.ENABLED_IS -> Icons.Rounded.CheckBox - TriStateFilter.ENABLED_NOT -> Icons.Rounded.DisabledByDefault - }, - contentDescription = null, - tint = if (!enabled || state == TriStateFilter.DISABLED) { - MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha) - } else { - when (onClick) { - null -> MaterialTheme.colorScheme.onSurface.copy(alpha = ContentAlpha.disabled) - else -> MaterialTheme.colorScheme.primary - } - }, - ) - Text( - text = label, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = stateAlpha), - style = MaterialTheme.typography.bodyMedium, - ) - } -} - -@Composable -fun SelectItem( - label: String, - options: Array, - selectedIndex: Int, - onSelect: (Int) -> Unit, -) { - var expanded by remember { mutableStateOf(false) } - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = !expanded }, - ) { - OutlinedTextField( - modifier = Modifier - .menuAnchor() - .fillMaxWidth() - .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical), - label = { Text(text = label) }, - value = options[selectedIndex].toString(), - onValueChange = {}, - readOnly = true, - singleLine = true, - trailingIcon = { - ExposedDropdownMenuDefaults.TrailingIcon( - expanded = expanded, - ) - }, - colors = ExposedDropdownMenuDefaults.textFieldColors(), - ) - - ExposedDropdownMenu( - modifier = Modifier.exposedDropdownSize(matchTextFieldWidth = true), - expanded = expanded, - onDismissRequest = { expanded = false }, - ) { - options.forEachIndexed { index, text -> - DropdownMenuItem( - text = { Text(text.toString()) }, - onClick = { - onSelect(index) - expanded = false - }, - ) - } - } - } -} diff --git a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt index 8e2531544f..d91f8fc046 100644 --- a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt @@ -14,21 +14,21 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import eu.kanade.presentation.components.TabbedDialog import eu.kanade.presentation.components.TabbedDialogPaddings -import eu.kanade.presentation.components.TriStateItem import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel +import tachiyomi.core.preference.TriState import tachiyomi.domain.category.model.Category import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibrarySort import tachiyomi.domain.library.model.sort import tachiyomi.domain.library.service.LibraryPreferences -import tachiyomi.domain.manga.model.TriStateFilter import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.HeadingItem import tachiyomi.presentation.core.components.RadioItem import tachiyomi.presentation.core.components.SliderItem import tachiyomi.presentation.core.components.SortItem +import tachiyomi.presentation.core.components.TriStateItem @Composable fun LibrarySettingsDialog( @@ -74,7 +74,7 @@ private fun ColumnScope.FilterPage( TriStateItem( label = stringResource(R.string.label_downloaded), state = if (downloadedOnly) { - TriStateFilter.ENABLED_IS + TriState.ENABLED_IS } else { filterDownloaded }, diff --git a/app/src/main/java/eu/kanade/presentation/manga/ChapterSettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/manga/ChapterSettingsDialog.kt index 665906e716..8b9ef54a38 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/ChapterSettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/ChapterSettingsDialog.kt @@ -27,20 +27,20 @@ import eu.kanade.domain.manga.model.downloadedFilter import eu.kanade.domain.manga.model.forceDownloaded import eu.kanade.presentation.components.TabbedDialog import eu.kanade.presentation.components.TabbedDialogPaddings -import eu.kanade.presentation.components.TriStateItem import eu.kanade.tachiyomi.R +import tachiyomi.core.preference.TriState import tachiyomi.domain.manga.model.Manga -import tachiyomi.domain.manga.model.TriStateFilter import tachiyomi.presentation.core.components.RadioItem import tachiyomi.presentation.core.components.SortItem +import tachiyomi.presentation.core.components.TriStateItem @Composable fun ChapterSettingsDialog( onDismissRequest: () -> Unit, manga: Manga? = null, - onDownloadFilterChanged: (TriStateFilter) -> Unit, - onUnreadFilterChanged: (TriStateFilter) -> Unit, - onBookmarkedFilterChanged: (TriStateFilter) -> Unit, + onDownloadFilterChanged: (TriState) -> Unit, + onUnreadFilterChanged: (TriState) -> Unit, + onBookmarkedFilterChanged: (TriState) -> Unit, onSortModeChanged: (Long) -> Unit, onDisplayModeChanged: (Long) -> Unit, onSetAsDefault: (applyToExistingManga: Boolean) -> Unit, @@ -78,11 +78,11 @@ fun ChapterSettingsDialog( when (page) { 0 -> { FilterPage( - downloadFilter = manga?.downloadedFilter ?: TriStateFilter.DISABLED, + downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED, onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true }, - unreadFilter = manga?.unreadFilter ?: TriStateFilter.DISABLED, + unreadFilter = manga?.unreadFilter ?: TriState.DISABLED, onUnreadFilterChanged = onUnreadFilterChanged, - bookmarkedFilter = manga?.bookmarkedFilter ?: TriStateFilter.DISABLED, + bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED, onBookmarkedFilterChanged = onBookmarkedFilterChanged, ) } @@ -106,12 +106,12 @@ fun ChapterSettingsDialog( @Composable private fun ColumnScope.FilterPage( - downloadFilter: TriStateFilter, - onDownloadFilterChanged: ((TriStateFilter) -> Unit)?, - unreadFilter: TriStateFilter, - onUnreadFilterChanged: (TriStateFilter) -> Unit, - bookmarkedFilter: TriStateFilter, - onBookmarkedFilterChanged: (TriStateFilter) -> Unit, + downloadFilter: TriState, + onDownloadFilterChanged: ((TriState) -> Unit)?, + unreadFilter: TriState, + onUnreadFilterChanged: (TriState) -> Unit, + bookmarkedFilter: TriState, + onBookmarkedFilterChanged: (TriState) -> Unit, ) { TriStateItem( label = stringResource(R.string.label_downloaded), diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index cc9bd506e4..c5a66afab5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -21,12 +21,11 @@ import eu.kanade.tachiyomi.util.system.isReleaseBuildType import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.workManager import tachiyomi.core.preference.PreferenceStore +import tachiyomi.core.preference.TriState import tachiyomi.core.preference.getEnum import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED -import tachiyomi.domain.manga.model.TriStateFilter -import uy.kohesive.injekt.api.get import java.io.File object Migrations { @@ -350,12 +349,12 @@ object Migrations { remove(key) val newValue = when (pref.get()) { - 1 -> TriStateFilter.ENABLED_IS - 2 -> TriStateFilter.ENABLED_NOT - else -> TriStateFilter.DISABLED + 1 -> TriState.ENABLED_IS + 2 -> TriState.ENABLED_NOT + else -> TriState.DISABLED } - preferenceStore.getEnum("${key}_v2", TriStateFilter.DISABLED).set(newValue) + preferenceStore.getEnum("${key}_v2", TriState.DISABLED).set(newValue) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterDialog.kt index 20eb63a80a..149194d931 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterDialog.kt @@ -16,17 +16,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import eu.kanade.presentation.components.AdaptiveSheet -import eu.kanade.presentation.components.SelectItem -import eu.kanade.presentation.components.TriStateItem import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList -import tachiyomi.domain.manga.model.TriStateFilter +import tachiyomi.core.preference.TriState import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.CollapsibleBox import tachiyomi.presentation.core.components.HeadingItem +import tachiyomi.presentation.core.components.SelectItem import tachiyomi.presentation.core.components.SortItem import tachiyomi.presentation.core.components.TextItem +import tachiyomi.presentation.core.components.TriStateItem import tachiyomi.presentation.core.components.material.Button import tachiyomi.presentation.core.components.material.Divider @@ -164,19 +164,19 @@ private fun FilterItem(filter: Filter<*>, onUpdate: () -> Unit) { } } -private fun Int.toTriStateFilter(): TriStateFilter { +private fun Int.toTriStateFilter(): TriState { return when (this) { - Filter.TriState.STATE_IGNORE -> TriStateFilter.DISABLED - Filter.TriState.STATE_INCLUDE -> TriStateFilter.ENABLED_IS - Filter.TriState.STATE_EXCLUDE -> TriStateFilter.ENABLED_NOT + Filter.TriState.STATE_IGNORE -> TriState.DISABLED + Filter.TriState.STATE_INCLUDE -> TriState.ENABLED_IS + Filter.TriState.STATE_EXCLUDE -> TriState.ENABLED_NOT else -> throw IllegalStateException("Unknown TriState state: $this") } } -private fun TriStateFilter.toTriStateInt(): Int { +private fun TriState.toTriStateInt(): Int { return when (this) { - TriStateFilter.DISABLED -> Filter.TriState.STATE_IGNORE - TriStateFilter.ENABLED_IS -> Filter.TriState.STATE_INCLUDE - TriStateFilter.ENABLED_NOT -> Filter.TriState.STATE_EXCLUDE + TriState.DISABLED -> Filter.TriState.STATE_IGNORE + TriState.ENABLED_IS -> Filter.TriState.STATE_INCLUDE + TriState.ENABLED_NOT -> Filter.TriState.STATE_EXCLUDE } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt index fa982d3225..5649285b27 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt @@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import tachiyomi.core.preference.CheckboxState +import tachiyomi.core.preference.TriState import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.withIOContext @@ -57,7 +58,6 @@ import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.manga.interactor.GetLibraryManga import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaUpdate -import tachiyomi.domain.manga.model.TriStateFilter import tachiyomi.domain.manga.model.applyFilter import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.track.interactor.GetTracksPerManga @@ -153,7 +153,7 @@ class LibraryScreenModel( prefs.filterBookmarked, prefs.filterCompleted, ) + trackFilter.values - ).any { it != TriStateFilter.DISABLED } + ).any { it != TriState.DISABLED } } .distinctUntilChanged() .onEach { @@ -169,12 +169,12 @@ class LibraryScreenModel( */ private suspend fun LibraryMap.applyFilters( trackMap: Map>, - loggedInTrackServices: Map, + loggedInTrackServices: Map, ): LibraryMap { val prefs = getLibraryItemPreferencesFlow().first() val downloadedOnly = prefs.globalFilterDownloaded val filterDownloaded = - if (downloadedOnly) TriStateFilter.ENABLED_IS else prefs.filterDownloaded + if (downloadedOnly) TriState.ENABLED_IS else prefs.filterDownloaded val filterUnread = prefs.filterUnread val filterStarted = prefs.filterStarted val filterBookmarked = prefs.filterBookmarked @@ -182,8 +182,8 @@ class LibraryScreenModel( val isNotLoggedInAnyTrack = loggedInTrackServices.isEmpty() - val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateFilter.ENABLED_NOT) it.key else null } - val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateFilter.ENABLED_IS) it.key else null } + val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null } + val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null } val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty() val filterFnDownloaded: (LibraryItem) -> Boolean = { @@ -308,11 +308,11 @@ class LibraryScreenModel( localBadge = it[1] as Boolean, languageBadge = it[2] as Boolean, globalFilterDownloaded = it[3] as Boolean, - filterDownloaded = it[4] as TriStateFilter, - filterUnread = it[5] as TriStateFilter, - filterStarted = it[6] as TriStateFilter, - filterBookmarked = it[7] as TriStateFilter, - filterCompleted = it[8] as TriStateFilter, + filterDownloaded = it[4] as TriState, + filterUnread = it[5] as TriState, + filterStarted = it[6] as TriState, + filterBookmarked = it[7] as TriState, + filterCompleted = it[8] as TriState, ) }, ) @@ -365,7 +365,7 @@ class LibraryScreenModel( * * @return map of track id with the filter value */ - private fun getTrackingFilterFlow(): Flow> { + private fun getTrackingFilterFlow(): Flow> { val loggedServices = trackManager.services.filter { it.isLogged } return if (loggedServices.isNotEmpty()) { val prefFlows = loggedServices @@ -670,11 +670,11 @@ class LibraryScreenModel( val languageBadge: Boolean, val globalFilterDownloaded: Boolean, - val filterDownloaded: TriStateFilter, - val filterUnread: TriStateFilter, - val filterStarted: TriStateFilter, - val filterBookmarked: TriStateFilter, - val filterCompleted: TriStateFilter, + val filterDownloaded: TriState, + val filterUnread: TriState, + val filterStarted: TriState, + val filterBookmarked: TriState, + val filterCompleted: TriState, ) @Immutable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt index 78badbedeb..12db1e289c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt @@ -6,6 +6,7 @@ import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.util.preference.toggle import tachiyomi.core.preference.Preference +import tachiyomi.core.preference.TriState import tachiyomi.core.preference.getAndSet import tachiyomi.core.util.lang.launchIO import tachiyomi.domain.category.interactor.SetDisplayMode @@ -14,7 +15,6 @@ import tachiyomi.domain.category.model.Category import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibrarySort import tachiyomi.domain.library.service.LibraryPreferences -import tachiyomi.domain.manga.model.TriStateFilter import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -33,7 +33,7 @@ class LibrarySettingsScreenModel( preference(libraryPreferences).toggle() } - fun toggleFilter(preference: (LibraryPreferences) -> Preference) { + fun toggleFilter(preference: (LibraryPreferences) -> Preference) { preference(libraryPreferences).getAndSet { it.next() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index 5f78f9726a..ca8bf5c638 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -46,6 +46,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import logcat.LogPriority import tachiyomi.core.preference.CheckboxState +import tachiyomi.core.preference.TriState import tachiyomi.core.preference.mapAsCheckboxState import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNonCancellable @@ -67,7 +68,6 @@ import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga import tachiyomi.domain.manga.interactor.GetMangaWithChapters import tachiyomi.domain.manga.interactor.SetMangaChapterFlags import tachiyomi.domain.manga.model.Manga -import tachiyomi.domain.manga.model.TriStateFilter import tachiyomi.domain.manga.model.applyFilter import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.track.interactor.GetTracks @@ -743,13 +743,13 @@ class MangaInfoScreenModel( * Sets the read filter and requests an UI update. * @param state whether to display only unread chapters or all chapters. */ - fun setUnreadFilter(state: TriStateFilter) { + fun setUnreadFilter(state: TriState) { val manga = successState?.manga ?: return val flag = when (state) { - TriStateFilter.DISABLED -> Manga.SHOW_ALL - TriStateFilter.ENABLED_IS -> Manga.CHAPTER_SHOW_UNREAD - TriStateFilter.ENABLED_NOT -> Manga.CHAPTER_SHOW_READ + TriState.DISABLED -> Manga.SHOW_ALL + TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_UNREAD + TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_READ } coroutineScope.launchNonCancellable { setMangaChapterFlags.awaitSetUnreadFilter(manga, flag) @@ -760,13 +760,13 @@ class MangaInfoScreenModel( * Sets the download filter and requests an UI update. * @param state whether to display only downloaded chapters or all chapters. */ - fun setDownloadedFilter(state: TriStateFilter) { + fun setDownloadedFilter(state: TriState) { val manga = successState?.manga ?: return val flag = when (state) { - TriStateFilter.DISABLED -> Manga.SHOW_ALL - TriStateFilter.ENABLED_IS -> Manga.CHAPTER_SHOW_DOWNLOADED - TriStateFilter.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_DOWNLOADED + TriState.DISABLED -> Manga.SHOW_ALL + TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_DOWNLOADED + TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_DOWNLOADED } coroutineScope.launchNonCancellable { @@ -778,13 +778,13 @@ class MangaInfoScreenModel( * Sets the bookmark filter and requests an UI update. * @param state whether to display only bookmarked chapters or all chapters. */ - fun setBookmarkedFilter(state: TriStateFilter) { + fun setBookmarkedFilter(state: TriState) { val manga = successState?.manga ?: return val flag = when (state) { - TriStateFilter.DISABLED -> Manga.SHOW_ALL - TriStateFilter.ENABLED_IS -> Manga.CHAPTER_SHOW_BOOKMARKED - TriStateFilter.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_BOOKMARKED + TriState.DISABLED -> Manga.SHOW_ALL + TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_BOOKMARKED + TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_BOOKMARKED } coroutineScope.launchNonCancellable { diff --git a/core/src/main/java/tachiyomi/core/preference/TriState.kt b/core/src/main/java/tachiyomi/core/preference/TriState.kt new file mode 100644 index 0000000000..68b9173ce0 --- /dev/null +++ b/core/src/main/java/tachiyomi/core/preference/TriState.kt @@ -0,0 +1,16 @@ +package tachiyomi.core.preference + +enum class TriState { + DISABLED, // Disable filter + ENABLED_IS, // Enabled with "is" filter + ENABLED_NOT, // Enabled with "not" filter + ; + + fun next(): TriState { + return when (this) { + DISABLED -> ENABLED_IS + ENABLED_IS -> ENABLED_NOT + ENABLED_NOT -> DISABLED + } + } +} diff --git a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt index 7aa496c544..efec8755b4 100644 --- a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt @@ -1,11 +1,11 @@ package tachiyomi.domain.library.service import tachiyomi.core.preference.PreferenceStore +import tachiyomi.core.preference.TriState import tachiyomi.core.preference.getEnum import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibrarySort import tachiyomi.domain.manga.model.Manga -import tachiyomi.domain.manga.model.TriStateFilter class LibraryPreferences( private val preferenceStore: PreferenceStore, @@ -49,27 +49,27 @@ class LibraryPreferences( // region Filter - fun filterDownloaded() = preferenceStore.getEnum("pref_filter_library_downloaded_v2", TriStateFilter.DISABLED) + fun filterDownloaded() = preferenceStore.getEnum("pref_filter_library_downloaded_v2", TriState.DISABLED) - fun filterUnread() = preferenceStore.getEnum("pref_filter_library_unread_v2", TriStateFilter.DISABLED) + fun filterUnread() = preferenceStore.getEnum("pref_filter_library_unread_v2", TriState.DISABLED) - fun filterStarted() = preferenceStore.getEnum("pref_filter_library_started_v2", TriStateFilter.DISABLED) + fun filterStarted() = preferenceStore.getEnum("pref_filter_library_started_v2", TriState.DISABLED) - fun filterBookmarked() = preferenceStore.getEnum("pref_filter_library_bookmarked_v2", TriStateFilter.DISABLED) + fun filterBookmarked() = preferenceStore.getEnum("pref_filter_library_bookmarked_v2", TriState.DISABLED) - fun filterCompleted() = preferenceStore.getEnum("pref_filter_library_completed_v2", TriStateFilter.DISABLED) + fun filterCompleted() = preferenceStore.getEnum("pref_filter_library_completed_v2", TriState.DISABLED) - fun filterIntervalCustom() = preferenceStore.getEnum("pref_filter_library_interval_custom", TriStateFilter.DISABLED) + fun filterIntervalCustom() = preferenceStore.getEnum("pref_filter_library_interval_custom", TriState.DISABLED) - fun filterIntervalLong() = preferenceStore.getEnum("pref_filter_library_interval_long", TriStateFilter.DISABLED) + fun filterIntervalLong() = preferenceStore.getEnum("pref_filter_library_interval_long", TriState.DISABLED) - fun filterIntervalLate() = preferenceStore.getEnum("pref_filter_library_interval_late", TriStateFilter.DISABLED) + fun filterIntervalLate() = preferenceStore.getEnum("pref_filter_library_interval_late", TriState.DISABLED) - fun filterIntervalDropped() = preferenceStore.getEnum("pref_filter_library_interval_dropped", TriStateFilter.DISABLED) + fun filterIntervalDropped() = preferenceStore.getEnum("pref_filter_library_interval_dropped", TriState.DISABLED) - fun filterIntervalPassed() = preferenceStore.getEnum("pref_filter_library_interval_passed", TriStateFilter.DISABLED) + fun filterIntervalPassed() = preferenceStore.getEnum("pref_filter_library_interval_passed", TriState.DISABLED) - fun filterTracking(id: Int) = preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriStateFilter.DISABLED) + fun filterTracking(id: Int) = preferenceStore.getEnum("pref_filter_library_tracked_${id}_v2", TriState.DISABLED) // endregion diff --git a/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt b/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt index 22e953b588..41db5f8273 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt @@ -1,6 +1,7 @@ package tachiyomi.domain.manga.model import eu.kanade.tachiyomi.source.model.UpdateStrategy +import tachiyomi.core.preference.TriState import java.io.Serializable data class Manga( @@ -43,18 +44,18 @@ data class Manga( val bookmarkedFilterRaw: Long get() = chapterFlags and CHAPTER_BOOKMARKED_MASK - val unreadFilter: TriStateFilter + val unreadFilter: TriState get() = when (unreadFilterRaw) { - CHAPTER_SHOW_UNREAD -> TriStateFilter.ENABLED_IS - CHAPTER_SHOW_READ -> TriStateFilter.ENABLED_NOT - else -> TriStateFilter.DISABLED + CHAPTER_SHOW_UNREAD -> TriState.ENABLED_IS + CHAPTER_SHOW_READ -> TriState.ENABLED_NOT + else -> TriState.DISABLED } - val bookmarkedFilter: TriStateFilter + val bookmarkedFilter: TriState get() = when (bookmarkedFilterRaw) { - CHAPTER_SHOW_BOOKMARKED -> TriStateFilter.ENABLED_IS - CHAPTER_SHOW_NOT_BOOKMARKED -> TriStateFilter.ENABLED_NOT - else -> TriStateFilter.DISABLED + CHAPTER_SHOW_BOOKMARKED -> TriState.ENABLED_IS + CHAPTER_SHOW_NOT_BOOKMARKED -> TriState.ENABLED_NOT + else -> TriState.DISABLED } fun sortDescending(): Boolean { diff --git a/domain/src/main/java/tachiyomi/domain/manga/model/TriState.kt b/domain/src/main/java/tachiyomi/domain/manga/model/TriState.kt new file mode 100644 index 0000000000..75a21f9597 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/manga/model/TriState.kt @@ -0,0 +1,9 @@ +package tachiyomi.domain.manga.model + +import tachiyomi.core.preference.TriState + +inline fun applyFilter(filter: TriState, predicate: () -> Boolean): Boolean = when (filter) { + TriState.DISABLED -> true + TriState.ENABLED_IS -> predicate() + TriState.ENABLED_NOT -> !predicate() +} diff --git a/domain/src/main/java/tachiyomi/domain/manga/model/TriStateFilter.kt b/domain/src/main/java/tachiyomi/domain/manga/model/TriStateFilter.kt deleted file mode 100644 index 8a86316832..0000000000 --- a/domain/src/main/java/tachiyomi/domain/manga/model/TriStateFilter.kt +++ /dev/null @@ -1,22 +0,0 @@ -package tachiyomi.domain.manga.model - -enum class TriStateFilter { - DISABLED, // Disable filter - ENABLED_IS, // Enabled with "is" filter - ENABLED_NOT, // Enabled with "not" filter - ; - - fun next(): TriStateFilter { - return when (this) { - DISABLED -> ENABLED_IS - ENABLED_IS -> ENABLED_NOT - ENABLED_NOT -> DISABLED - } - } -} - -inline fun applyFilter(filter: TriStateFilter, predicate: () -> Boolean): Boolean = when (filter) { - TriStateFilter.DISABLED -> true - TriStateFilter.ENABLED_IS -> predicate() - TriStateFilter.ENABLED_NOT -> !predicate() -} diff --git a/presentation-core/build.gradle.kts b/presentation-core/build.gradle.kts index 53025fdaaf..9c87d74657 100644 --- a/presentation-core/build.gradle.kts +++ b/presentation-core/build.gradle.kts @@ -21,6 +21,8 @@ android { } dependencies { + implementation(project(":core")) + // Compose implementation(platform(compose.bom)) implementation(compose.activity) diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt index fae6c83829..d0442ae549 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt @@ -10,10 +10,17 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material.ContentAlpha import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDownward import androidx.compose.material.icons.filled.ArrowUpward +import androidx.compose.material.icons.rounded.CheckBox +import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank +import androidx.compose.material.icons.rounded.DisabledByDefault import androidx.compose.material3.Checkbox +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField @@ -21,11 +28,16 @@ import androidx.compose.material3.RadioButton import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import tachiyomi.core.preference.TriState import tachiyomi.presentation.core.theme.header object SettingsItemsPaddings { @@ -175,22 +187,99 @@ fun SliderItem( } @Composable -private fun BaseSettingsItem( +fun SelectItem( label: String, - widget: @Composable RowScope.() -> Unit, - onClick: () -> Unit, + options: Array, + selectedIndex: Int, + onSelect: (Int) -> Unit, +) { + var expanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + ) { + OutlinedTextField( + modifier = Modifier + .menuAnchor() + .fillMaxWidth() + .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical), + label = { Text(text = label) }, + value = options[selectedIndex].toString(), + onValueChange = {}, + readOnly = true, + singleLine = true, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon( + expanded = expanded, + ) + }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + + ExposedDropdownMenu( + modifier = Modifier.exposedDropdownSize(matchTextFieldWidth = true), + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + options.forEachIndexed { index, text -> + DropdownMenuItem( + text = { Text(text.toString()) }, + onClick = { + onSelect(index) + expanded = false + }, + ) + } + } + } +} + +@Composable +fun TriStateItem( + label: String, + state: TriState, + enabled: Boolean = true, + onClick: ((TriState) -> Unit)?, ) { Row( modifier = Modifier - .clickable(onClick = onClick) + .clickable( + enabled = enabled && onClick != null, + onClick = { + when (state) { + TriState.DISABLED -> onClick?.invoke(TriState.ENABLED_IS) + TriState.ENABLED_IS -> onClick?.invoke(TriState.ENABLED_NOT) + TriState.ENABLED_NOT -> onClick?.invoke(TriState.DISABLED) + } + }, + ) .fillMaxWidth() .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(24.dp), ) { - widget(this) + val stateAlpha = if (enabled && onClick != null) 1f else ContentAlpha.disabled + + Icon( + imageVector = when (state) { + TriState.DISABLED -> Icons.Rounded.CheckBoxOutlineBlank + TriState.ENABLED_IS -> Icons.Rounded.CheckBox + TriState.ENABLED_NOT -> Icons.Rounded.DisabledByDefault + }, + contentDescription = null, + tint = if (!enabled || state == TriState.DISABLED) { + MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha) + } else { + when (onClick) { + null -> MaterialTheme.colorScheme.onSurface.copy(alpha = ContentAlpha.disabled) + else -> MaterialTheme.colorScheme.primary + } + }, + ) Text( text = label, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = stateAlpha), style = MaterialTheme.typography.bodyMedium, ) } @@ -212,3 +301,25 @@ fun TextItem( singleLine = true, ) } + +@Composable +private fun BaseSettingsItem( + label: String, + widget: @Composable RowScope.() -> Unit, + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .clickable(onClick = onClick) + .fillMaxWidth() + .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = SettingsItemsPaddings.Vertical), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(24.dp), + ) { + widget(this) + Text( + text = label, + style = MaterialTheme.typography.bodyMedium, + ) + } +}