mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Migrate library settings sheet to Compose
This commit is contained in:
		| @@ -4,7 +4,7 @@ import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI | ||||
| import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD | ||||
| import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED | ||||
| import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ | ||||
| import eu.kanade.tachiyomi.widget.ExtendedNavigationView | ||||
| import eu.kanade.tachiyomi.widget.TriState | ||||
| import tachiyomi.core.preference.PreferenceStore | ||||
| import tachiyomi.domain.library.model.LibraryDisplayMode | ||||
| import tachiyomi.domain.library.model.LibrarySort | ||||
| @@ -36,17 +36,17 @@ class LibraryPreferences( | ||||
|  | ||||
|     // region Filter | ||||
|  | ||||
|     fun filterDownloaded() = preferenceStore.getInt("pref_filter_library_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.DISABLED.value) | ||||
|     fun filterDownloaded() = preferenceStore.getInt("pref_filter_library_downloaded", TriState.DISABLED.value) | ||||
|  | ||||
|     fun filterUnread() = preferenceStore.getInt("pref_filter_library_unread", ExtendedNavigationView.Item.TriStateGroup.State.DISABLED.value) | ||||
|     fun filterUnread() = preferenceStore.getInt("pref_filter_library_unread", TriState.DISABLED.value) | ||||
|  | ||||
|     fun filterStarted() = preferenceStore.getInt("pref_filter_library_started", ExtendedNavigationView.Item.TriStateGroup.State.DISABLED.value) | ||||
|     fun filterStarted() = preferenceStore.getInt("pref_filter_library_started", TriState.DISABLED.value) | ||||
|  | ||||
|     fun filterBookmarked() = preferenceStore.getInt("pref_filter_library_bookmarked", ExtendedNavigationView.Item.TriStateGroup.State.DISABLED.value) | ||||
|     fun filterBookmarked() = preferenceStore.getInt("pref_filter_library_bookmarked", TriState.DISABLED.value) | ||||
|  | ||||
|     fun filterCompleted() = preferenceStore.getInt("pref_filter_library_completed", ExtendedNavigationView.Item.TriStateGroup.State.DISABLED.value) | ||||
|     fun filterCompleted() = preferenceStore.getInt("pref_filter_library_completed", TriState.DISABLED.value) | ||||
|  | ||||
|     fun filterTracking(name: Int) = preferenceStore.getInt("pref_filter_library_tracked_$name", ExtendedNavigationView.Item.TriStateGroup.State.DISABLED.value) | ||||
|     fun filterTracking(name: Int) = preferenceStore.getInt("pref_filter_library_tracked_$name", TriState.DISABLED.value) | ||||
|  | ||||
|     // endregion | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package eu.kanade.presentation.components | ||||
|  | ||||
| import androidx.annotation.StringRes | ||||
| import androidx.compose.foundation.clickable | ||||
| import androidx.compose.foundation.layout.Arrangement | ||||
| import androidx.compose.foundation.layout.Row | ||||
| @@ -14,6 +15,7 @@ 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.Icon | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.material3.RadioButton | ||||
| @@ -21,19 +23,35 @@ import androidx.compose.material3.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.unit.dp | ||||
| import tachiyomi.domain.manga.model.TriStateFilter | ||||
| import tachiyomi.presentation.core.theme.header | ||||
|  | ||||
| @Composable | ||||
| fun HeadingItem( | ||||
|     @StringRes labelRes: Int, | ||||
| ) { | ||||
|     Text( | ||||
|         text = stringResource(labelRes), | ||||
|         style = MaterialTheme.typography.header, | ||||
|         modifier = Modifier | ||||
|             .fillMaxWidth() | ||||
|             .padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp), | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| fun TriStateItem( | ||||
|     label: String, | ||||
|     state: TriStateFilter, | ||||
|     enabled: Boolean = true, | ||||
|     onClick: ((TriStateFilter) -> Unit)?, | ||||
| ) { | ||||
|     Row( | ||||
|         modifier = Modifier | ||||
|             .clickable( | ||||
|                 enabled = onClick != null, | ||||
|                 enabled = enabled && onClick != null, | ||||
|                 onClick = { | ||||
|                     when (state) { | ||||
|                         TriStateFilter.DISABLED -> onClick?.invoke(TriStateFilter.ENABLED_IS) | ||||
| @@ -47,7 +65,7 @@ fun TriStateItem( | ||||
|         verticalAlignment = Alignment.CenterVertically, | ||||
|         horizontalArrangement = Arrangement.spacedBy(24.dp), | ||||
|     ) { | ||||
|         val stateAlpha = if (onClick != null) 1f else ContentAlpha.disabled | ||||
|         val stateAlpha = if (enabled && onClick != null) 1f else ContentAlpha.disabled | ||||
|  | ||||
|         Icon( | ||||
|             imageVector = when (state) { | ||||
| @@ -56,7 +74,7 @@ fun TriStateItem( | ||||
|                 TriStateFilter.ENABLED_NOT -> Icons.Rounded.DisabledByDefault | ||||
|             }, | ||||
|             contentDescription = null, | ||||
|             tint = if (state == TriStateFilter.DISABLED) { | ||||
|             tint = if (!enabled || state == TriStateFilter.DISABLED) { | ||||
|                 MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha) | ||||
|             } else { | ||||
|                 when (onClick) { | ||||
| @@ -109,6 +127,31 @@ fun SortItem( | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| fun CheckboxItem( | ||||
|     label: String, | ||||
|     checked: Boolean, | ||||
|     onClick: () -> Unit, | ||||
| ) { | ||||
|     Row( | ||||
|         modifier = Modifier | ||||
|             .clickable(onClick = onClick) | ||||
|             .fillMaxWidth() | ||||
|             .padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp), | ||||
|         verticalAlignment = Alignment.CenterVertically, | ||||
|         horizontalArrangement = Arrangement.spacedBy(24.dp), | ||||
|     ) { | ||||
|         Checkbox( | ||||
|             checked = checked, | ||||
|             onCheckedChange = null, | ||||
|         ) | ||||
|         Text( | ||||
|             text = label, | ||||
|             style = MaterialTheme.typography.bodyMedium, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| fun RadioItem( | ||||
|     label: String, | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| package eu.kanade.presentation.components | ||||
|  | ||||
| import androidx.compose.animation.animateContentSize | ||||
| import androidx.compose.foundation.layout.Box | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.ColumnScope | ||||
| import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.foundation.layout.heightIn | ||||
| import androidx.compose.foundation.layout.wrapContentSize | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.filled.MoreVert | ||||
| @@ -20,12 +20,9 @@ import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.runtime.rememberCoroutineScope | ||||
| import androidx.compose.runtime.saveable.rememberSaveable | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.layout.onSizeChanged | ||||
| import androidx.compose.ui.platform.LocalDensity | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.compose.ui.util.fastForEachIndexed | ||||
| @@ -85,26 +82,13 @@ fun TabbedDialog( | ||||
|             } | ||||
|             Divider() | ||||
|  | ||||
|             val density = LocalDensity.current | ||||
|             var largestHeight by rememberSaveable { mutableStateOf(0f) } | ||||
|             HorizontalPager( | ||||
|                 modifier = Modifier.heightIn(min = largestHeight.dp), | ||||
|                 modifier = Modifier.animateContentSize(), | ||||
|                 count = tabTitles.size, | ||||
|                 state = pagerState, | ||||
|                 verticalAlignment = Alignment.Top, | ||||
|             ) { page -> | ||||
|                 Box( | ||||
|                     modifier = Modifier.onSizeChanged { | ||||
|                         with(density) { | ||||
|                             val heightDp = it.height.toDp() | ||||
|                             if (heightDp.value > largestHeight) { | ||||
|                                 largestHeight = heightDp.value | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                 ) { | ||||
|                     content(contentPadding, page) | ||||
|                 } | ||||
|                 content(contentPadding, page) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,249 @@ | ||||
| package eu.kanade.presentation.library | ||||
|  | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.ColumnScope | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.rememberScrollState | ||||
| import androidx.compose.foundation.verticalScroll | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.collectAsState | ||||
| import androidx.compose.runtime.derivedStateOf | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import eu.kanade.domain.library.service.LibraryPreferences | ||||
| import eu.kanade.presentation.components.CheckboxItem | ||||
| import eu.kanade.presentation.components.HeadingItem | ||||
| import eu.kanade.presentation.components.RadioItem | ||||
| import eu.kanade.presentation.components.SortItem | ||||
| 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 eu.kanade.tachiyomi.widget.toTriStateFilter | ||||
| import tachiyomi.domain.category.model.Category | ||||
| import tachiyomi.domain.library.model.LibraryDisplayMode | ||||
| import tachiyomi.domain.library.model.LibrarySort | ||||
| import tachiyomi.domain.library.model.display | ||||
| import tachiyomi.domain.library.model.sort | ||||
| import tachiyomi.domain.manga.model.TriStateFilter | ||||
|  | ||||
| @Composable | ||||
| fun LibrarySettingsDialog( | ||||
|     onDismissRequest: () -> Unit, | ||||
|     screenModel: LibrarySettingsScreenModel, | ||||
|     activeCategoryIndex: Int, | ||||
| ) { | ||||
|     val state by screenModel.state.collectAsState() | ||||
|     val category by remember(activeCategoryIndex) { | ||||
|         derivedStateOf { state.categories[activeCategoryIndex] } | ||||
|     } | ||||
|  | ||||
|     TabbedDialog( | ||||
|         onDismissRequest = onDismissRequest, | ||||
|         tabTitles = listOf( | ||||
|             stringResource(R.string.action_filter), | ||||
|             stringResource(R.string.action_sort), | ||||
|             stringResource(R.string.action_display), | ||||
|         ), | ||||
|     ) { contentPadding, page -> | ||||
|         Column( | ||||
|             modifier = Modifier | ||||
|                 .padding(contentPadding) | ||||
|                 .padding(vertical = TabbedDialogPaddings.Vertical) | ||||
|                 .verticalScroll(rememberScrollState()), | ||||
|         ) { | ||||
|             when (page) { | ||||
|                 0 -> FilterPage( | ||||
|                     screenModel = screenModel, | ||||
|                 ) | ||||
|                 1 -> SortPage( | ||||
|                     category = category, | ||||
|                     screenModel = screenModel, | ||||
|                 ) | ||||
|                 2 -> DisplayPage( | ||||
|                     category = category, | ||||
|                     screenModel = screenModel, | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| private fun ColumnScope.FilterPage( | ||||
|     screenModel: LibrarySettingsScreenModel, | ||||
| ) { | ||||
|     val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState() | ||||
|     val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState() | ||||
|     TriStateItem( | ||||
|         label = stringResource(R.string.label_downloaded), | ||||
|         state = if (downloadedOnly) { | ||||
|             TriStateFilter.ENABLED_IS | ||||
|         } else { | ||||
|             filterDownloaded.toTriStateFilter() | ||||
|         }, | ||||
|         enabled = !downloadedOnly, | ||||
|         onClick = { screenModel.toggleFilter(LibraryPreferences::filterDownloaded) }, | ||||
|     ) | ||||
|     val filterUnread by screenModel.libraryPreferences.filterUnread().collectAsState() | ||||
|     TriStateItem( | ||||
|         label = stringResource(R.string.action_filter_unread), | ||||
|         state = filterUnread.toTriStateFilter(), | ||||
|         onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) }, | ||||
|     ) | ||||
|     val filterStarted by screenModel.libraryPreferences.filterStarted().collectAsState() | ||||
|     TriStateItem( | ||||
|         label = stringResource(R.string.label_started), | ||||
|         state = filterStarted.toTriStateFilter(), | ||||
|         onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) }, | ||||
|     ) | ||||
|     val filterBookmarked by screenModel.libraryPreferences.filterBookmarked().collectAsState() | ||||
|     TriStateItem( | ||||
|         label = stringResource(R.string.action_filter_bookmarked), | ||||
|         state = filterBookmarked.toTriStateFilter(), | ||||
|         onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) }, | ||||
|     ) | ||||
|     val filterCompleted by screenModel.libraryPreferences.filterCompleted().collectAsState() | ||||
|     TriStateItem( | ||||
|         label = stringResource(R.string.completed), | ||||
|         state = filterCompleted.toTriStateFilter(), | ||||
|         onClick = { screenModel.toggleFilter(LibraryPreferences::filterCompleted) }, | ||||
|     ) | ||||
|  | ||||
|     when (screenModel.trackServices.size) { | ||||
|         0 -> { | ||||
|             // No trackers | ||||
|         } | ||||
|         1 -> { | ||||
|             val service = screenModel.trackServices[0] | ||||
|             val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState() | ||||
|             TriStateItem( | ||||
|                 label = stringResource(R.string.action_filter_tracked), | ||||
|                 state = filterTracker.toTriStateFilter(), | ||||
|                 onClick = { screenModel.toggleTracker(service.id.toInt()) }, | ||||
|             ) | ||||
|         } | ||||
|         else -> { | ||||
|             HeadingItem(R.string.action_filter_tracked) | ||||
|             screenModel.trackServices.map { service -> | ||||
|                 val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState() | ||||
|                 TriStateItem( | ||||
|                     label = stringResource(service.nameRes()), | ||||
|                     state = filterTracker.toTriStateFilter(), | ||||
|                     onClick = { screenModel.toggleTracker(service.id.toInt()) }, | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| private fun ColumnScope.SortPage( | ||||
|     category: Category, | ||||
|     screenModel: LibrarySettingsScreenModel, | ||||
| ) { | ||||
|     val sortingMode = category.sort.type | ||||
|     val sortDescending = !category.sort.isAscending | ||||
|  | ||||
|     listOf( | ||||
|         R.string.action_sort_alpha to LibrarySort.Type.Alphabetical, | ||||
|         R.string.action_sort_total to LibrarySort.Type.TotalChapters, | ||||
|         R.string.action_sort_last_read to LibrarySort.Type.LastRead, | ||||
|         R.string.action_sort_last_manga_update to LibrarySort.Type.LastUpdate, | ||||
|         R.string.action_sort_unread_count to LibrarySort.Type.UnreadCount, | ||||
|         R.string.action_sort_latest_chapter to LibrarySort.Type.LatestChapter, | ||||
|         R.string.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate, | ||||
|         R.string.action_sort_date_added to LibrarySort.Type.DateAdded, | ||||
|     ).map { (titleRes, mode) -> | ||||
|         SortItem( | ||||
|             label = stringResource(titleRes), | ||||
|             sortDescending = sortDescending.takeIf { sortingMode == mode }, | ||||
|             onClick = { | ||||
|                 val isTogglingDirection = sortingMode == mode | ||||
|                 val direction = when { | ||||
|                     isTogglingDirection -> if (sortDescending) LibrarySort.Direction.Ascending else LibrarySort.Direction.Descending | ||||
|                     else -> if (sortDescending) LibrarySort.Direction.Descending else LibrarySort.Direction.Ascending | ||||
|                 } | ||||
|                 screenModel.setSort(category, mode, direction) | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| private fun ColumnScope.DisplayPage( | ||||
|     category: Category, | ||||
|     screenModel: LibrarySettingsScreenModel, | ||||
| ) { | ||||
|     HeadingItem(R.string.action_display_mode) | ||||
|     listOf( | ||||
|         R.string.action_display_grid to LibraryDisplayMode.CompactGrid, | ||||
|         R.string.action_display_comfortable_grid to LibraryDisplayMode.ComfortableGrid, | ||||
|         R.string.action_display_cover_only_grid to LibraryDisplayMode.CoverOnlyGrid, | ||||
|         R.string.action_display_list to LibraryDisplayMode.List, | ||||
|     ).map { (titleRes, mode) -> | ||||
|         RadioItem( | ||||
|             label = stringResource(titleRes), | ||||
|             selected = category.display == mode, | ||||
|             onClick = { screenModel.setDisplayMode(category, mode) }, | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     HeadingItem(R.string.badges_header) | ||||
|     val downloadBadge by screenModel.libraryPreferences.downloadBadge().collectAsState() | ||||
|     CheckboxItem( | ||||
|         label = stringResource(R.string.action_display_download_badge), | ||||
|         checked = downloadBadge, | ||||
|         onClick = { | ||||
|             screenModel.togglePreference(LibraryPreferences::downloadBadge) | ||||
|         }, | ||||
|     ) | ||||
|     val localBadge by screenModel.libraryPreferences.localBadge().collectAsState() | ||||
|     CheckboxItem( | ||||
|         label = stringResource(R.string.action_display_local_badge), | ||||
|         checked = localBadge, | ||||
|         onClick = { | ||||
|             screenModel.togglePreference(LibraryPreferences::localBadge) | ||||
|         }, | ||||
|     ) | ||||
|     val languageBadge by screenModel.libraryPreferences.languageBadge().collectAsState() | ||||
|     CheckboxItem( | ||||
|         label = stringResource(R.string.action_display_language_badge), | ||||
|         checked = languageBadge, | ||||
|         onClick = { | ||||
|             screenModel.togglePreference(LibraryPreferences::languageBadge) | ||||
|         }, | ||||
|     ) | ||||
|  | ||||
|     HeadingItem(R.string.tabs_header) | ||||
|     val categoryTabs by screenModel.libraryPreferences.categoryTabs().collectAsState() | ||||
|     CheckboxItem( | ||||
|         label = stringResource(R.string.action_display_show_tabs), | ||||
|         checked = categoryTabs, | ||||
|         onClick = { | ||||
|             screenModel.togglePreference(LibraryPreferences::categoryTabs) | ||||
|         }, | ||||
|     ) | ||||
|     val categoryNumberOfItems by screenModel.libraryPreferences.categoryNumberOfItems().collectAsState() | ||||
|     CheckboxItem( | ||||
|         label = stringResource(R.string.action_display_show_number_of_items), | ||||
|         checked = categoryNumberOfItems, | ||||
|         onClick = { | ||||
|             screenModel.togglePreference(LibraryPreferences::categoryNumberOfItems) | ||||
|         }, | ||||
|     ) | ||||
|  | ||||
|     HeadingItem(R.string.other_header) | ||||
|     val showContinueReadingButton by screenModel.libraryPreferences.showContinueReadingButton().collectAsState() | ||||
|     CheckboxItem( | ||||
|         label = stringResource(R.string.action_display_show_continue_reading_button), | ||||
|         checked = showContinueReadingButton, | ||||
|         onClick = { | ||||
|             screenModel.togglePreference(LibraryPreferences::showContinueReadingButton) | ||||
|         }, | ||||
|     ) | ||||
| } | ||||
| @@ -23,7 +23,7 @@ import eu.kanade.tachiyomi.util.preference.minusAssign | ||||
| import eu.kanade.tachiyomi.util.preference.plusAssign | ||||
| import eu.kanade.tachiyomi.util.system.DeviceUtil | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import eu.kanade.tachiyomi.widget.ExtendedNavigationView | ||||
| import eu.kanade.tachiyomi.widget.TriState | ||||
| import tachiyomi.core.preference.PreferenceStore | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| @@ -114,9 +114,9 @@ object Migrations { | ||||
|                 fun convertBooleanPrefToTriState(key: String): Int { | ||||
|                     val oldPrefValue = prefs.getBoolean(key, false) | ||||
|                     return if (oldPrefValue) { | ||||
|                         ExtendedNavigationView.Item.TriStateGroup.State.ENABLED_IS.value | ||||
|                         TriState.ENABLED_IS.value | ||||
|                     } else { | ||||
|                         ExtendedNavigationView.Item.TriStateGroup.State.DISABLED.value | ||||
|                         TriState.DISABLED.value | ||||
|                     } | ||||
|                 } | ||||
|                 prefs.edit { | ||||
|   | ||||
| @@ -33,7 +33,7 @@ import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.util.chapter.getNextUnread | ||||
| import eu.kanade.tachiyomi.util.removeCovers | ||||
| import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup | ||||
| import eu.kanade.tachiyomi.widget.TriState | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.collectLatest | ||||
| import kotlinx.coroutines.flow.combine | ||||
| @@ -149,8 +149,8 @@ class LibraryScreenModel( | ||||
|                     prefs.filterStarted or | ||||
|                     prefs.filterBookmarked or | ||||
|                     prefs.filterCompleted | ||||
|                 ) != TriStateGroup.State.DISABLED.value | ||||
|             val b = trackFilter.values.any { it != TriStateGroup.State.DISABLED.value } | ||||
|                 ) != TriState.DISABLED.value | ||||
|             val b = trackFilter.values.any { it != TriState.DISABLED.value } | ||||
|             a || b | ||||
|         } | ||||
|             .distinctUntilChanged() | ||||
| @@ -179,17 +179,17 @@ class LibraryScreenModel( | ||||
|  | ||||
|         val isNotLoggedInAnyTrack = loggedInTrackServices.isEmpty() | ||||
|  | ||||
|         val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateGroup.State.ENABLED_NOT.value) it.key else null } | ||||
|         val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriStateGroup.State.ENABLED_IS.value) it.key else null } | ||||
|         val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_NOT.value) it.key else null } | ||||
|         val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_IS.value) it.key else null } | ||||
|         val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty() | ||||
|  | ||||
|         val filterFnDownloaded: (LibraryItem) -> Boolean = downloaded@{ | ||||
|             if (!downloadedOnly && filterDownloaded == TriStateGroup.State.DISABLED.value) return@downloaded true | ||||
|             if (!downloadedOnly && filterDownloaded == TriState.DISABLED.value) return@downloaded true | ||||
|  | ||||
|             val isDownloaded = it.libraryManga.manga.isLocal() || | ||||
|                 it.downloadCount > 0 || | ||||
|                 downloadManager.getDownloadCount(it.libraryManga.manga) > 0 | ||||
|             return@downloaded if (downloadedOnly || filterDownloaded == TriStateGroup.State.ENABLED_IS.value) { | ||||
|             return@downloaded if (downloadedOnly || filterDownloaded == TriState.ENABLED_IS.value) { | ||||
|                 isDownloaded | ||||
|             } else { | ||||
|                 !isDownloaded | ||||
| @@ -197,10 +197,10 @@ class LibraryScreenModel( | ||||
|         } | ||||
|  | ||||
|         val filterFnUnread: (LibraryItem) -> Boolean = unread@{ | ||||
|             if (filterUnread == TriStateGroup.State.DISABLED.value) return@unread true | ||||
|             if (filterUnread == TriState.DISABLED.value) return@unread true | ||||
|  | ||||
|             val isUnread = it.libraryManga.unreadCount > 0 | ||||
|             return@unread if (filterUnread == TriStateGroup.State.ENABLED_IS.value) { | ||||
|             return@unread if (filterUnread == TriState.ENABLED_IS.value) { | ||||
|                 isUnread | ||||
|             } else { | ||||
|                 !isUnread | ||||
| @@ -208,10 +208,10 @@ class LibraryScreenModel( | ||||
|         } | ||||
|  | ||||
|         val filterFnStarted: (LibraryItem) -> Boolean = started@{ | ||||
|             if (filterStarted == TriStateGroup.State.DISABLED.value) return@started true | ||||
|             if (filterStarted == TriState.DISABLED.value) return@started true | ||||
|  | ||||
|             val hasStarted = it.libraryManga.hasStarted | ||||
|             return@started if (filterStarted == TriStateGroup.State.ENABLED_IS.value) { | ||||
|             return@started if (filterStarted == TriState.ENABLED_IS.value) { | ||||
|                 hasStarted | ||||
|             } else { | ||||
|                 !hasStarted | ||||
| @@ -219,10 +219,10 @@ class LibraryScreenModel( | ||||
|         } | ||||
|  | ||||
|         val filterFnBookmarked: (LibraryItem) -> Boolean = bookmarked@{ | ||||
|             if (filterBookmarked == TriStateGroup.State.DISABLED.value) return@bookmarked true | ||||
|             if (filterBookmarked == TriState.DISABLED.value) return@bookmarked true | ||||
|  | ||||
|             val hasBookmarks = it.libraryManga.hasBookmarks | ||||
|             return@bookmarked if (filterBookmarked == TriStateGroup.State.ENABLED_IS.value) { | ||||
|             return@bookmarked if (filterBookmarked == TriState.ENABLED_IS.value) { | ||||
|                 hasBookmarks | ||||
|             } else { | ||||
|                 !hasBookmarks | ||||
| @@ -230,10 +230,10 @@ class LibraryScreenModel( | ||||
|         } | ||||
|  | ||||
|         val filterFnCompleted: (LibraryItem) -> Boolean = completed@{ | ||||
|             if (filterCompleted == TriStateGroup.State.DISABLED.value) return@completed true | ||||
|             if (filterCompleted == TriState.DISABLED.value) return@completed true | ||||
|  | ||||
|             val isCompleted = it.libraryManga.manga.status.toInt() == SManga.COMPLETED | ||||
|             return@completed if (filterCompleted == TriStateGroup.State.ENABLED_IS.value) { | ||||
|             return@completed if (filterCompleted == TriState.ENABLED_IS.value) { | ||||
|                 isCompleted | ||||
|             } else { | ||||
|                 !isCompleted | ||||
| @@ -572,6 +572,10 @@ class LibraryScreenModel( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun showSettingsDialog() { | ||||
|         mutableState.update { it.copy(dialog = Dialog.SettingsSheet) } | ||||
|     } | ||||
|  | ||||
|     fun clearSelection() { | ||||
|         mutableState.update { it.copy(selection = emptyList()) } | ||||
|     } | ||||
| @@ -690,6 +694,7 @@ class LibraryScreenModel( | ||||
|     } | ||||
|  | ||||
|     sealed class Dialog { | ||||
|         object SettingsSheet : Dialog() | ||||
|         data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog() | ||||
|         data class DeleteManga(val manga: List<Manga>) : Dialog() | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,84 @@ | ||||
| package eu.kanade.tachiyomi.ui.library | ||||
|  | ||||
| import androidx.compose.runtime.Immutable | ||||
| import cafe.adriel.voyager.core.model.StateScreenModel | ||||
| import cafe.adriel.voyager.core.model.coroutineScope | ||||
| import eu.kanade.domain.base.BasePreferences | ||||
| import eu.kanade.domain.category.interactor.SetDisplayModeForCategory | ||||
| import eu.kanade.domain.category.interactor.SetSortModeForCategory | ||||
| import eu.kanade.domain.library.service.LibraryPreferences | ||||
| import eu.kanade.tachiyomi.data.track.TrackManager | ||||
| import eu.kanade.tachiyomi.util.preference.toggle | ||||
| import eu.kanade.tachiyomi.widget.TriState | ||||
| import kotlinx.coroutines.flow.collectLatest | ||||
| import kotlinx.coroutines.flow.update | ||||
| import tachiyomi.core.preference.Preference | ||||
| import tachiyomi.core.preference.getAndSet | ||||
| import tachiyomi.core.util.lang.launchIO | ||||
| import tachiyomi.domain.category.interactor.GetCategories | ||||
| import tachiyomi.domain.category.model.Category | ||||
| import tachiyomi.domain.library.model.LibraryDisplayMode | ||||
| import tachiyomi.domain.library.model.LibrarySort | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
|  | ||||
| class LibrarySettingsScreenModel( | ||||
|     val preferences: BasePreferences = Injekt.get(), | ||||
|     val libraryPreferences: LibraryPreferences = Injekt.get(), | ||||
|     private val getCategories: GetCategories = Injekt.get(), | ||||
|     private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(), | ||||
|     private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(), | ||||
|     trackManager: TrackManager = Injekt.get(), | ||||
| ) : StateScreenModel<LibrarySettingsScreenModel.State>(State()) { | ||||
|  | ||||
|     val trackServices = trackManager.services.filter { service -> service.isLogged } | ||||
|  | ||||
|     init { | ||||
|         coroutineScope.launchIO { | ||||
|             getCategories.subscribe() | ||||
|                 .collectLatest { | ||||
|                     mutableState.update { state -> | ||||
|                         state.copy( | ||||
|                             categories = it, | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun togglePreference(preference: (LibraryPreferences) -> Preference<Boolean>) { | ||||
|         preference(libraryPreferences).toggle() | ||||
|     } | ||||
|  | ||||
|     fun toggleFilter(preference: (LibraryPreferences) -> Preference<Int>) { | ||||
|         preference(libraryPreferences).getAndSet { | ||||
|             when (it) { | ||||
|                 TriState.DISABLED.value -> TriState.ENABLED_IS.value | ||||
|                 TriState.ENABLED_IS.value -> TriState.ENABLED_NOT.value | ||||
|                 TriState.ENABLED_NOT.value -> TriState.DISABLED.value | ||||
|                 else -> throw IllegalStateException("Unknown TriStateGroup state: $this") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun toggleTracker(id: Int) { | ||||
|         toggleFilter { libraryPreferences.filterTracking(id) } | ||||
|     } | ||||
|  | ||||
|     fun setDisplayMode(category: Category, mode: LibraryDisplayMode) { | ||||
|         coroutineScope.launchIO { | ||||
|             setDisplayModeForCategory.await(category, mode) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun setSort(category: Category, mode: LibrarySort.Type, direction: LibrarySort.Direction) { | ||||
|         coroutineScope.launchIO { | ||||
|             setSortModeForCategory.await(category, mode, direction) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Immutable | ||||
|     data class State( | ||||
|         val categories: List<Category> = emptyList(), | ||||
|     ) | ||||
| } | ||||
| @@ -1,474 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.ui.library | ||||
|  | ||||
| import android.app.Activity | ||||
| import android.content.Context | ||||
| import android.util.AttributeSet | ||||
| import android.view.View | ||||
| import eu.kanade.domain.base.BasePreferences | ||||
| import eu.kanade.domain.category.interactor.SetDisplayModeForCategory | ||||
| import eu.kanade.domain.category.interactor.SetSortModeForCategory | ||||
| import eu.kanade.domain.library.service.LibraryPreferences | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.track.TrackManager | ||||
| import eu.kanade.tachiyomi.data.track.TrackService | ||||
| import eu.kanade.tachiyomi.widget.ExtendedNavigationView | ||||
| import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State | ||||
| import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.Job | ||||
| import tachiyomi.core.util.lang.launchIO | ||||
| import tachiyomi.domain.category.model.Category | ||||
| import tachiyomi.domain.library.model.LibraryDisplayMode | ||||
| import tachiyomi.domain.library.model.LibrarySort | ||||
| import tachiyomi.domain.library.model.display | ||||
| import tachiyomi.domain.library.model.sort | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import uy.kohesive.injekt.injectLazy | ||||
|  | ||||
| class LibrarySettingsSheet( | ||||
|     activity: Activity, | ||||
|     private val trackManager: TrackManager = Injekt.get(), | ||||
|     private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(), | ||||
|     private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(), | ||||
| ) : TabbedBottomSheetDialog(activity) { | ||||
|  | ||||
|     val filters: Filter | ||||
|     private val sort: Sort | ||||
|     private val display: Display | ||||
|  | ||||
|     val sheetScope = CoroutineScope(Job() + Dispatchers.IO) | ||||
|  | ||||
|     init { | ||||
|         filters = Filter(activity) | ||||
|         sort = Sort(activity) | ||||
|         display = Display(activity) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * adjusts selected button to match real state. | ||||
|      * @param currentCategory ID of currently shown category | ||||
|      */ | ||||
|     fun show(currentCategory: Category) { | ||||
|         filters.adjustFilterSelection() | ||||
|  | ||||
|         sort.currentCategory = currentCategory | ||||
|         sort.adjustDisplaySelection() | ||||
|  | ||||
|         display.currentCategory = currentCategory | ||||
|         display.adjustDisplaySelection() | ||||
|  | ||||
|         super.show() | ||||
|     } | ||||
|  | ||||
|     override fun getTabViews(): List<View> = listOf( | ||||
|         filters, | ||||
|         sort, | ||||
|         display, | ||||
|     ) | ||||
|  | ||||
|     override fun getTabTitles(): List<Int> = listOf( | ||||
|         R.string.action_filter, | ||||
|         R.string.action_sort, | ||||
|         R.string.action_display, | ||||
|     ) | ||||
|  | ||||
|     /** | ||||
|      * Filters group (unread, downloaded, ...). | ||||
|      */ | ||||
|     inner class Filter @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : | ||||
|         Settings(context, attrs) { | ||||
|  | ||||
|         private val filterGroup = FilterGroup() | ||||
|  | ||||
|         init { | ||||
|             setGroups(listOf(filterGroup)) | ||||
|         } | ||||
|  | ||||
|         // Refreshes Filter Setting selections | ||||
|         fun adjustFilterSelection() { | ||||
|             filterGroup.initModels() | ||||
|             filterGroup.items.forEach { adapter.notifyItemChanged(it) } | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Returns true if there's at least one filter from [FilterGroup] active. | ||||
|          */ | ||||
|         fun hasActiveFilters(): Boolean { | ||||
|             return filterGroup.items.filterIsInstance<Item.TriStateGroup>().any { it.state != State.DISABLED.value } | ||||
|         } | ||||
|  | ||||
|         inner class FilterGroup : Group { | ||||
|  | ||||
|             private val downloaded = Item.TriStateGroup(R.string.label_downloaded, this) | ||||
|             private val unread = Item.TriStateGroup(R.string.action_filter_unread, this) | ||||
|             private val started = Item.TriStateGroup(R.string.label_started, this) | ||||
|             private val bookmarked = Item.TriStateGroup(R.string.action_filter_bookmarked, this) | ||||
|             private val completed = Item.TriStateGroup(R.string.completed, this) | ||||
|             private val trackFilters: Map<Long, Item.TriStateGroup> | ||||
|  | ||||
|             override val header = null | ||||
|             override val items: List<Item> | ||||
|             override val footer = null | ||||
|  | ||||
|             init { | ||||
|                 trackManager.services.filter { service -> service.isLogged } | ||||
|                     .also { services -> | ||||
|                         val size = services.size | ||||
|                         trackFilters = services.associate { service -> | ||||
|                             Pair(service.id, Item.TriStateGroup(getServiceResId(service, size), this)) | ||||
|                         } | ||||
|                         val list: MutableList<Item> = mutableListOf(downloaded, unread, started, bookmarked, completed) | ||||
|                         if (size > 1) list.add(Item.Header(R.string.action_filter_tracked)) | ||||
|                         list.addAll(trackFilters.values) | ||||
|                         items = list | ||||
|                     } | ||||
|             } | ||||
|  | ||||
|             private fun getServiceResId(service: TrackService, size: Int): Int { | ||||
|                 return if (size > 1) service.nameRes() else R.string.action_filter_tracked | ||||
|             } | ||||
|  | ||||
|             override fun initModels() { | ||||
|                 if (preferences.downloadedOnly().get()) { | ||||
|                     downloaded.state = State.ENABLED_IS.value | ||||
|                     downloaded.enabled = false | ||||
|                 } else { | ||||
|                     downloaded.state = libraryPreferences.filterDownloaded().get() | ||||
|                     downloaded.enabled = true | ||||
|                 } | ||||
|                 unread.state = libraryPreferences.filterUnread().get() | ||||
|                 started.state = libraryPreferences.filterStarted().get() | ||||
|                 bookmarked.state = libraryPreferences.filterBookmarked().get() | ||||
|                 completed.state = libraryPreferences.filterCompleted().get() | ||||
|  | ||||
|                 trackFilters.forEach { trackFilter -> | ||||
|                     trackFilter.value.state = libraryPreferences.filterTracking(trackFilter.key.toInt()).get() | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             override fun onItemClicked(item: Item) { | ||||
|                 item as Item.TriStateGroup | ||||
|                 val newState = when (item.state) { | ||||
|                     State.DISABLED.value -> State.ENABLED_IS.value | ||||
|                     State.ENABLED_IS.value -> State.ENABLED_NOT.value | ||||
|                     State.ENABLED_NOT.value -> State.DISABLED.value | ||||
|                     else -> throw Exception("Unknown State") | ||||
|                 } | ||||
|                 item.state = newState | ||||
|                 when (item) { | ||||
|                     downloaded -> libraryPreferences.filterDownloaded().set(newState) | ||||
|                     unread -> libraryPreferences.filterUnread().set(newState) | ||||
|                     started -> libraryPreferences.filterStarted().set(newState) | ||||
|                     bookmarked -> libraryPreferences.filterBookmarked().set(newState) | ||||
|                     completed -> libraryPreferences.filterCompleted().set(newState) | ||||
|                     else -> { | ||||
|                         trackFilters.forEach { trackFilter -> | ||||
|                             if (trackFilter.value == item) { | ||||
|                                 libraryPreferences.filterTracking(trackFilter.key.toInt()).set(newState) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 adapter.notifyItemChanged(item) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sorting group (alphabetically, by last read, ...) and ascending or descending. | ||||
|      */ | ||||
|     inner class Sort @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : | ||||
|         Settings(context, attrs) { | ||||
|  | ||||
|         private val sort = SortGroup() | ||||
|  | ||||
|         init { | ||||
|             setGroups(listOf(sort)) | ||||
|         } | ||||
|  | ||||
|         // Refreshes Display Setting selections | ||||
|         fun adjustDisplaySelection() { | ||||
|             sort.initModels() | ||||
|             sort.items.forEach { adapter.notifyItemChanged(it) } | ||||
|         } | ||||
|  | ||||
|         inner class SortGroup : Group { | ||||
|  | ||||
|             private val alphabetically = Item.MultiSort(R.string.action_sort_alpha, this) | ||||
|             private val total = Item.MultiSort(R.string.action_sort_total, this) | ||||
|             private val lastRead = Item.MultiSort(R.string.action_sort_last_read, this) | ||||
|             private val lastChecked = Item.MultiSort(R.string.action_sort_last_manga_update, this) | ||||
|             private val unread = Item.MultiSort(R.string.action_sort_unread_count, this) | ||||
|             private val latestChapter = Item.MultiSort(R.string.action_sort_latest_chapter, this) | ||||
|             private val chapterFetchDate = Item.MultiSort(R.string.action_sort_chapter_fetch_date, this) | ||||
|             private val dateAdded = Item.MultiSort(R.string.action_sort_date_added, this) | ||||
|  | ||||
|             override val header = null | ||||
|             override val items = | ||||
|                 listOf(alphabetically, lastRead, lastChecked, unread, total, latestChapter, chapterFetchDate, dateAdded) | ||||
|             override val footer = null | ||||
|  | ||||
|             override fun initModels() { | ||||
|                 val sort = currentCategory.sort | ||||
|                 val order = if (sort.isAscending) Item.MultiSort.SORT_ASC else Item.MultiSort.SORT_DESC | ||||
|  | ||||
|                 alphabetically.state = | ||||
|                     if (sort.type == LibrarySort.Type.Alphabetical) order else Item.MultiSort.SORT_NONE | ||||
|                 lastRead.state = | ||||
|                     if (sort.type == LibrarySort.Type.LastRead) order else Item.MultiSort.SORT_NONE | ||||
|                 lastChecked.state = | ||||
|                     if (sort.type == LibrarySort.Type.LastUpdate) order else Item.MultiSort.SORT_NONE | ||||
|                 unread.state = | ||||
|                     if (sort.type == LibrarySort.Type.UnreadCount) order else Item.MultiSort.SORT_NONE | ||||
|                 total.state = | ||||
|                     if (sort.type == LibrarySort.Type.TotalChapters) order else Item.MultiSort.SORT_NONE | ||||
|                 latestChapter.state = | ||||
|                     if (sort.type == LibrarySort.Type.LatestChapter) order else Item.MultiSort.SORT_NONE | ||||
|                 chapterFetchDate.state = | ||||
|                     if (sort.type == LibrarySort.Type.ChapterFetchDate) order else Item.MultiSort.SORT_NONE | ||||
|                 dateAdded.state = | ||||
|                     if (sort.type == LibrarySort.Type.DateAdded) order else Item.MultiSort.SORT_NONE | ||||
|             } | ||||
|  | ||||
|             override fun onItemClicked(item: Item) { | ||||
|                 item as Item.MultiStateGroup | ||||
|                 val prevState = item.state | ||||
|  | ||||
|                 item.group.items.forEach { | ||||
|                     (it as Item.MultiStateGroup).state = | ||||
|                         Item.MultiSort.SORT_NONE | ||||
|                 } | ||||
|                 item.state = when (prevState) { | ||||
|                     Item.MultiSort.SORT_NONE -> Item.MultiSort.SORT_ASC | ||||
|                     Item.MultiSort.SORT_ASC -> Item.MultiSort.SORT_DESC | ||||
|                     Item.MultiSort.SORT_DESC -> Item.MultiSort.SORT_ASC | ||||
|                     else -> throw Exception("Unknown state") | ||||
|                 } | ||||
|  | ||||
|                 setSortPreference(item) | ||||
|  | ||||
|                 item.group.items.forEach { adapter.notifyItemChanged(it) } | ||||
|             } | ||||
|  | ||||
|             private fun setSortPreference(item: Item.MultiStateGroup) { | ||||
|                 val mode = when (item) { | ||||
|                     alphabetically -> LibrarySort.Type.Alphabetical | ||||
|                     lastRead -> LibrarySort.Type.LastRead | ||||
|                     lastChecked -> LibrarySort.Type.LastUpdate | ||||
|                     unread -> LibrarySort.Type.UnreadCount | ||||
|                     total -> LibrarySort.Type.TotalChapters | ||||
|                     latestChapter -> LibrarySort.Type.LatestChapter | ||||
|                     chapterFetchDate -> LibrarySort.Type.ChapterFetchDate | ||||
|                     dateAdded -> LibrarySort.Type.DateAdded | ||||
|                     else -> throw NotImplementedError("Unknown display mode") | ||||
|                 } | ||||
|                 val direction = if (item.state == Item.MultiSort.SORT_ASC) { | ||||
|                     LibrarySort.Direction.Ascending | ||||
|                 } else { | ||||
|                     LibrarySort.Direction.Descending | ||||
|                 } | ||||
|  | ||||
|                 sheetScope.launchIO { | ||||
|                     setSortModeForCategory.await(currentCategory!!, mode, direction) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Display group, to show the library as a list or a grid. | ||||
|      */ | ||||
|     inner class Display @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : | ||||
|         Settings(context, attrs) { | ||||
|  | ||||
|         private val displayGroup: DisplayGroup | ||||
|         private val badgeGroup: BadgeGroup | ||||
|         private val tabsGroup: TabsGroup | ||||
|         private val otherGroup: OtherGroup | ||||
|  | ||||
|         init { | ||||
|             displayGroup = DisplayGroup() | ||||
|             badgeGroup = BadgeGroup() | ||||
|             tabsGroup = TabsGroup() | ||||
|             otherGroup = OtherGroup() | ||||
|             setGroups(listOf(displayGroup, badgeGroup, tabsGroup, otherGroup)) | ||||
|         } | ||||
|  | ||||
|         // Refreshes Display Setting selections | ||||
|         fun adjustDisplaySelection() { | ||||
|             val mode = getDisplayModePreference() | ||||
|             displayGroup.setGroupSelections(mode) | ||||
|             displayGroup.items.forEach { adapter.notifyItemChanged(it) } | ||||
|         } | ||||
|  | ||||
|         // Gets user preference of currently selected display mode at current category | ||||
|         private fun getDisplayModePreference(): LibraryDisplayMode { | ||||
|             return currentCategory.display | ||||
|         } | ||||
|  | ||||
|         inner class DisplayGroup : Group { | ||||
|  | ||||
|             private val compactGrid = Item.Radio(R.string.action_display_grid, this) | ||||
|             private val comfortableGrid = Item.Radio(R.string.action_display_comfortable_grid, this) | ||||
|             private val coverOnlyGrid = Item.Radio(R.string.action_display_cover_only_grid, this) | ||||
|             private val list = Item.Radio(R.string.action_display_list, this) | ||||
|  | ||||
|             override val header = Item.Header(R.string.action_display_mode) | ||||
|             override val items = listOf(compactGrid, comfortableGrid, coverOnlyGrid, list) | ||||
|             override val footer = null | ||||
|  | ||||
|             override fun initModels() { | ||||
|                 val mode = getDisplayModePreference() | ||||
|                 setGroupSelections(mode) | ||||
|             } | ||||
|  | ||||
|             override fun onItemClicked(item: Item) { | ||||
|                 item as Item.Radio | ||||
|                 if (item.checked) return | ||||
|  | ||||
|                 item.group.items.forEach { (it as Item.Radio).checked = false } | ||||
|                 item.checked = true | ||||
|  | ||||
|                 setDisplayModePreference(item) | ||||
|  | ||||
|                 item.group.items.forEach { adapter.notifyItemChanged(it) } | ||||
|             } | ||||
|  | ||||
|             // Sets display group selections based on given mode | ||||
|             fun setGroupSelections(mode: LibraryDisplayMode) { | ||||
|                 compactGrid.checked = mode == LibraryDisplayMode.CompactGrid | ||||
|                 comfortableGrid.checked = mode == LibraryDisplayMode.ComfortableGrid | ||||
|                 coverOnlyGrid.checked = mode == LibraryDisplayMode.CoverOnlyGrid | ||||
|                 list.checked = mode == LibraryDisplayMode.List | ||||
|             } | ||||
|  | ||||
|             private fun setDisplayModePreference(item: Item) { | ||||
|                 val flag = when (item) { | ||||
|                     compactGrid -> LibraryDisplayMode.CompactGrid | ||||
|                     comfortableGrid -> LibraryDisplayMode.ComfortableGrid | ||||
|                     coverOnlyGrid -> LibraryDisplayMode.CoverOnlyGrid | ||||
|                     list -> LibraryDisplayMode.List | ||||
|                     else -> throw NotImplementedError("Unknown display mode") | ||||
|                 } | ||||
|  | ||||
|                 sheetScope.launchIO { | ||||
|                     setDisplayModeForCategory.await(currentCategory!!, flag) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         inner class BadgeGroup : Group { | ||||
|             private val downloadBadge = Item.CheckboxGroup(R.string.action_display_download_badge, this) | ||||
|             private val localBadge = Item.CheckboxGroup(R.string.action_display_local_badge, this) | ||||
|             private val languageBadge = Item.CheckboxGroup(R.string.action_display_language_badge, this) | ||||
|  | ||||
|             override val header = Item.Header(R.string.badges_header) | ||||
|             override val items = listOf(downloadBadge, localBadge, languageBadge) | ||||
|             override val footer = null | ||||
|  | ||||
|             override fun initModels() { | ||||
|                 downloadBadge.checked = libraryPreferences.downloadBadge().get() | ||||
|                 localBadge.checked = libraryPreferences.localBadge().get() | ||||
|                 languageBadge.checked = libraryPreferences.languageBadge().get() | ||||
|             } | ||||
|  | ||||
|             override fun onItemClicked(item: Item) { | ||||
|                 item as Item.CheckboxGroup | ||||
|                 item.checked = !item.checked | ||||
|                 when (item) { | ||||
|                     downloadBadge -> libraryPreferences.downloadBadge().set((item.checked)) | ||||
|                     localBadge -> libraryPreferences.localBadge().set((item.checked)) | ||||
|                     languageBadge -> libraryPreferences.languageBadge().set((item.checked)) | ||||
|                     else -> {} | ||||
|                 } | ||||
|                 adapter.notifyItemChanged(item) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         inner class TabsGroup : Group { | ||||
|             private val showTabs = Item.CheckboxGroup(R.string.action_display_show_tabs, this) | ||||
|             private val showNumberOfItems = Item.CheckboxGroup(R.string.action_display_show_number_of_items, this) | ||||
|  | ||||
|             override val header = Item.Header(R.string.tabs_header) | ||||
|             override val items = listOf(showTabs, showNumberOfItems) | ||||
|             override val footer = null | ||||
|  | ||||
|             override fun initModels() { | ||||
|                 showTabs.checked = libraryPreferences.categoryTabs().get() | ||||
|                 showNumberOfItems.checked = libraryPreferences.categoryNumberOfItems().get() | ||||
|             } | ||||
|  | ||||
|             override fun onItemClicked(item: Item) { | ||||
|                 item as Item.CheckboxGroup | ||||
|                 item.checked = !item.checked | ||||
|                 when (item) { | ||||
|                     showTabs -> libraryPreferences.categoryTabs().set(item.checked) | ||||
|                     showNumberOfItems -> libraryPreferences.categoryNumberOfItems().set(item.checked) | ||||
|                     else -> {} | ||||
|                 } | ||||
|                 adapter.notifyItemChanged(item) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         inner class OtherGroup : Group { | ||||
|             private val showContinueReadingButton = Item.CheckboxGroup(R.string.action_display_show_continue_reading_button, this) | ||||
|  | ||||
|             override val header = Item.Header(R.string.other_header) | ||||
|             override val items = listOf(showContinueReadingButton) | ||||
|             override val footer = null | ||||
|  | ||||
|             override fun initModels() { | ||||
|                 showContinueReadingButton.checked = libraryPreferences.showContinueReadingButton().get() | ||||
|             } | ||||
|  | ||||
|             override fun onItemClicked(item: Item) { | ||||
|                 item as Item.CheckboxGroup | ||||
|                 item.checked = !item.checked | ||||
|                 when (item) { | ||||
|                     showContinueReadingButton -> libraryPreferences.showContinueReadingButton().set(item.checked) | ||||
|                     else -> {} | ||||
|                 } | ||||
|                 adapter.notifyItemChanged(item) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     open inner class Settings(context: Context, attrs: AttributeSet?) : | ||||
|         ExtendedNavigationView(context, attrs) { | ||||
|  | ||||
|         val preferences: BasePreferences by injectLazy() | ||||
|         val libraryPreferences: LibraryPreferences by injectLazy() | ||||
|         lateinit var adapter: Adapter | ||||
|  | ||||
|         /** | ||||
|          * Click listener to notify the parent fragment when an item from a group is clicked. | ||||
|          */ | ||||
|         var onGroupClicked: (Group) -> Unit = {} | ||||
|  | ||||
|         var currentCategory: Category? = null | ||||
|  | ||||
|         fun setGroups(groups: List<Group>) { | ||||
|             adapter = Adapter(groups.map { it.createItems() }.flatten()) | ||||
|             recycler.adapter = adapter | ||||
|  | ||||
|             groups.forEach { it.initModels() } | ||||
|             addView(recycler) | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Adapter of the recycler view. | ||||
|          */ | ||||
|         inner class Adapter(items: List<Item>) : ExtendedNavigationView.Adapter(items) { | ||||
|  | ||||
|             override fun onItemClicked(item: Item) { | ||||
|                 if (item is GroupedItem) { | ||||
|                     item.group.onItemClicked(item) | ||||
|                     onGroupClicked(item.group) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -33,6 +33,7 @@ import eu.kanade.presentation.category.ChangeCategoryDialog | ||||
| import eu.kanade.presentation.components.EmptyScreen | ||||
| import eu.kanade.presentation.components.EmptyScreenAction | ||||
| import eu.kanade.presentation.library.DeleteLibraryMangaDialog | ||||
| import eu.kanade.presentation.library.LibrarySettingsDialog | ||||
| import eu.kanade.presentation.library.components.LibraryContent | ||||
| import eu.kanade.presentation.library.components.LibraryToolbar | ||||
| import eu.kanade.presentation.manga.components.LibraryBottomActionMenu | ||||
| @@ -83,6 +84,7 @@ object LibraryTab : Tab { | ||||
|         val haptic = LocalHapticFeedback.current | ||||
|  | ||||
|         val screenModel = rememberScreenModel { LibraryScreenModel() } | ||||
|         val settingsScreenModel = rememberScreenModel { LibrarySettingsScreenModel() } | ||||
|         val state by screenModel.state.collectAsState() | ||||
|  | ||||
|         val snackbarHostState = remember { SnackbarHostState() } | ||||
| @@ -95,9 +97,6 @@ object LibraryTab : Tab { | ||||
|             } | ||||
|             started | ||||
|         } | ||||
|         val onClickFilter: () -> Unit = { | ||||
|             scope.launch { sendSettingsSheetIntent(state.categories[screenModel.activeCategoryIndex]) } | ||||
|         } | ||||
|  | ||||
|         Scaffold( | ||||
|             topBar = { scrollBehavior -> | ||||
| @@ -114,7 +113,7 @@ object LibraryTab : Tab { | ||||
|                     onClickUnselectAll = screenModel::clearSelection, | ||||
|                     onClickSelectAll = { screenModel.selectAll(screenModel.activeCategoryIndex) }, | ||||
|                     onClickInvertSelection = { screenModel.invertSelection(screenModel.activeCategoryIndex) }, | ||||
|                     onClickFilter = onClickFilter, | ||||
|                     onClickFilter = { screenModel.showSettingsDialog() }, | ||||
|                     onClickRefresh = { onClickRefresh(null) }, | ||||
|                     onClickOpenRandomManga = { | ||||
|                         scope.launch { | ||||
| @@ -201,6 +200,11 @@ object LibraryTab : Tab { | ||||
|  | ||||
|         val onDismissRequest = screenModel::closeDialog | ||||
|         when (val dialog = state.dialog) { | ||||
|             is LibraryScreenModel.Dialog.SettingsSheet -> LibrarySettingsDialog( | ||||
|                 onDismissRequest = onDismissRequest, | ||||
|                 screenModel = settingsScreenModel, | ||||
|                 activeCategoryIndex = screenModel.activeCategoryIndex, | ||||
|             ) | ||||
|             is LibraryScreenModel.Dialog.ChangeCategory -> { | ||||
|                 ChangeCategoryDialog( | ||||
|                     initialSelection = dialog.initialSelection, | ||||
| @@ -235,8 +239,8 @@ object LibraryTab : Tab { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         LaunchedEffect(state.selectionMode) { | ||||
|             HomeScreen.showBottomNav(!state.selectionMode) | ||||
|         LaunchedEffect(state.selectionMode, state.dialog) { | ||||
|             HomeScreen.showBottomNav(!state.selectionMode && state.dialog !is LibraryScreenModel.Dialog.SettingsSheet) | ||||
|         } | ||||
|  | ||||
|         LaunchedEffect(state.isLoading) { | ||||
| @@ -247,7 +251,7 @@ object LibraryTab : Tab { | ||||
|  | ||||
|         LaunchedEffect(Unit) { | ||||
|             launch { queryEvent.receiveAsFlow().collect(screenModel::search) } | ||||
|             launch { requestSettingsSheetEvent.receiveAsFlow().collectLatest { onClickFilter() } } | ||||
|             launch { requestSettingsSheetEvent.receiveAsFlow().collectLatest { screenModel.showSettingsDialog() } } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -257,8 +261,5 @@ object LibraryTab : Tab { | ||||
|  | ||||
|     // For opening settings sheet in LibraryController | ||||
|     private val requestSettingsSheetEvent = Channel<Unit>() | ||||
|     private val openSettingsSheetEvent_ = Channel<Category>() | ||||
|     val openSettingsSheetEvent = openSettingsSheetEvent_.receiveAsFlow() | ||||
|     private suspend fun sendSettingsSheetIntent(category: Category) = openSettingsSheetEvent_.send(category) | ||||
|     suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.send(Unit) | ||||
|     private suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.send(Unit) | ||||
| } | ||||
|   | ||||
| @@ -78,8 +78,6 @@ import eu.kanade.tachiyomi.ui.base.activity.BaseActivity | ||||
| import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen | ||||
| import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen | ||||
| import eu.kanade.tachiyomi.ui.home.HomeScreen | ||||
| import eu.kanade.tachiyomi.ui.library.LibrarySettingsSheet | ||||
| import eu.kanade.tachiyomi.ui.library.LibraryTab | ||||
| import eu.kanade.tachiyomi.ui.manga.MangaScreen | ||||
| import eu.kanade.tachiyomi.ui.more.NewUpdateScreen | ||||
| import eu.kanade.tachiyomi.util.system.dpToPx | ||||
| @@ -87,7 +85,6 @@ import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim | ||||
| import eu.kanade.tachiyomi.util.system.openInBrowser | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import eu.kanade.tachiyomi.util.view.setComposeContent | ||||
| import kotlinx.coroutines.cancel | ||||
| import kotlinx.coroutines.channels.awaitClose | ||||
| import kotlinx.coroutines.delay | ||||
| import kotlinx.coroutines.flow.callbackFlow | ||||
| @@ -100,7 +97,6 @@ import kotlinx.coroutines.launch | ||||
| import logcat.LogPriority | ||||
| import tachiyomi.core.Constants | ||||
| import tachiyomi.core.util.system.logcat | ||||
| import tachiyomi.domain.category.model.Category | ||||
| import tachiyomi.presentation.core.components.material.Scaffold | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| @@ -121,11 +117,6 @@ class MainActivity : BaseActivity() { | ||||
|     // To be checked by splash screen. If true then splash screen will be removed. | ||||
|     var ready = false | ||||
|  | ||||
|     /** | ||||
|      * Sheet containing filter/sort/display items. | ||||
|      */ | ||||
|     private var settingsSheet: LibrarySettingsSheet? = null | ||||
|  | ||||
|     private var navigator: Navigator? = null | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
| @@ -160,11 +151,6 @@ class MainActivity : BaseActivity() { | ||||
|         // Draw edge-to-edge | ||||
|         WindowCompat.setDecorFitsSystemWindows(window, false) | ||||
|  | ||||
|         settingsSheet = LibrarySettingsSheet(this) | ||||
|         LibraryTab.openSettingsSheetEvent | ||||
|             .onEach(::showSettingsSheet) | ||||
|             .launchIn(lifecycleScope) | ||||
|  | ||||
|         setComposeContent { | ||||
|             val incognito by preferences.incognitoMode().collectAsState() | ||||
|             val downloadOnly by preferences.downloadedOnly().collectAsState() | ||||
| @@ -303,14 +289,6 @@ class MainActivity : BaseActivity() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun showSettingsSheet(category: Category? = null) { | ||||
|         if (category != null) { | ||||
|             settingsSheet?.show(category) | ||||
|         } else { | ||||
|             lifecycleScope.launch { LibraryTab.requestOpenSettingsSheet() } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Composable | ||||
|     private fun ConfirmExit() { | ||||
|         val scope = rememberCoroutineScope() | ||||
| @@ -470,12 +448,6 @@ class MainActivity : BaseActivity() { | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     override fun onDestroy() { | ||||
|         settingsSheet?.sheetScope?.cancel() | ||||
|         settingsSheet = null | ||||
|         super.onDestroy() | ||||
|     } | ||||
|  | ||||
|     override fun onBackPressed() { | ||||
|         if (navigator?.size == 1 && | ||||
|             !onBackPressedDispatcher.hasEnabledCallbacks() && | ||||
|   | ||||
| @@ -1,270 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.widget | ||||
|  | ||||
| import android.content.Context | ||||
| import android.graphics.drawable.Drawable | ||||
| import android.util.AttributeSet | ||||
| import android.view.View.OnClickListener | ||||
| import android.view.ViewGroup | ||||
| import androidx.annotation.AttrRes | ||||
| import androidx.annotation.CallSuper | ||||
| import androidx.appcompat.content.res.AppCompatResources | ||||
| import androidx.core.view.isVisible | ||||
| import androidx.core.view.updatePadding | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.util.system.getResourceColor | ||||
|  | ||||
| /** | ||||
|  * An alternative implementation of [com.google.android.material.navigation.NavigationView], without menu | ||||
|  * inflation and allowing customizable items (multiple selections, custom views, etc). | ||||
|  */ | ||||
| open class ExtendedNavigationView @JvmOverloads constructor( | ||||
|     context: Context, | ||||
|     attrs: AttributeSet? = null, | ||||
|     defStyleAttr: Int = 0, | ||||
| ) : SimpleNavigationView(context, attrs, defStyleAttr) { | ||||
|  | ||||
|     /** | ||||
|      * Every item of the nav view. Generic items must belong to this list, custom items could be | ||||
|      * implemented by an abstract class. If more customization is needed in the future, this can be | ||||
|      * changed to an interface instead of sealed class. | ||||
|      */ | ||||
|     sealed class Item { | ||||
|         /** | ||||
|          * A view separator. | ||||
|          */ | ||||
|         class Separator(val paddingTop: Int = 0, val paddingBottom: Int = 0) : Item() | ||||
|  | ||||
|         /** | ||||
|          * A header with a title. | ||||
|          */ | ||||
|         class Header(val resTitle: Int) : Item() | ||||
|  | ||||
|         /** | ||||
|          * A checkbox. | ||||
|          */ | ||||
|         open class Checkbox(val resTitle: Int, var checked: Boolean = false, var enabled: Boolean = true) : Item() | ||||
|  | ||||
|         /** | ||||
|          * A checkbox belonging to a group. The group must handle selections and restrictions. | ||||
|          */ | ||||
|         class CheckboxGroup(resTitle: Int, override val group: Group, checked: Boolean = false, enabled: Boolean = true) : | ||||
|             Checkbox(resTitle, checked, enabled), GroupedItem | ||||
|  | ||||
|         /** | ||||
|          * A radio belonging to a group (a sole radio makes no sense). The group must handle | ||||
|          * selections and restrictions. | ||||
|          */ | ||||
|         class Radio(val resTitle: Int, override val group: Group, var checked: Boolean = false, var enabled: Boolean = true) : | ||||
|             Item(), GroupedItem | ||||
|  | ||||
|         /** | ||||
|          * An item with which needs more than two states (selected/deselected). | ||||
|          */ | ||||
|         abstract class MultiState(val resTitle: Int, var state: Int = 0, var enabled: Boolean = true, var isVisible: Boolean = true) : Item() { | ||||
|  | ||||
|             /** | ||||
|              * Returns the drawable associated to every possible each state. | ||||
|              */ | ||||
|             abstract fun getStateDrawable(context: Context): Drawable? | ||||
|  | ||||
|             /** | ||||
|              * Creates a vector tinted with the accent color. | ||||
|              * | ||||
|              * @param context any context. | ||||
|              * @param resId the vector resource to load and tint | ||||
|              */ | ||||
|             fun tintVector(context: Context, resId: Int, @AttrRes colorAttrRes: Int = R.attr.colorPrimary): Drawable { | ||||
|                 return AppCompatResources.getDrawable(context, resId)!!.apply { | ||||
|                     setTint(context.getResourceColor(if (enabled) colorAttrRes else R.attr.colorControlNormal)) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * An item with which needs more than two states (selected/deselected) belonging to a group. | ||||
|          * The group must handle selections and restrictions. | ||||
|          */ | ||||
|         abstract class MultiStateGroup(resTitle: Int, override val group: Group, state: Int = 0, enabled: Boolean = true) : | ||||
|             MultiState(resTitle, state, enabled), GroupedItem | ||||
|  | ||||
|         /** | ||||
|          * A multistate item for sorting lists (unselected, ascending, descending). | ||||
|          */ | ||||
|         class MultiSort(resId: Int, group: Group) : MultiStateGroup(resId, group) { | ||||
|  | ||||
|             companion object { | ||||
|                 const val SORT_NONE = 0 | ||||
|                 const val SORT_ASC = 1 | ||||
|                 const val SORT_DESC = 2 | ||||
|             } | ||||
|  | ||||
|             override fun getStateDrawable(context: Context): Drawable? { | ||||
|                 return when (state) { | ||||
|                     SORT_ASC -> tintVector(context, R.drawable.ic_arrow_up_white_32dp) | ||||
|                     SORT_DESC -> tintVector(context, R.drawable.ic_arrow_down_white_32dp) | ||||
|                     SORT_NONE -> AppCompatResources.getDrawable(context, R.drawable.empty_drawable_32dp) | ||||
|                     else -> null | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * A checkbox with 3 states (unselected, checked, explicitly unchecked). | ||||
|          */ | ||||
|         class TriStateGroup(resId: Int, group: Group) : MultiStateGroup(resId, group) { | ||||
|  | ||||
|             enum class State(val value: Int) { | ||||
|                 DISABLED(0), | ||||
|                 ENABLED_IS(1), | ||||
|                 ENABLED_NOT(2), | ||||
|             } | ||||
|  | ||||
|             override fun getStateDrawable(context: Context): Drawable? { | ||||
|                 return when (state) { | ||||
|                     State.DISABLED.value -> tintVector(context, R.drawable.ic_check_box_outline_blank_24dp, R.attr.colorControlNormal) | ||||
|                     State.ENABLED_IS.value -> tintVector(context, R.drawable.ic_check_box_24dp) | ||||
|                     State.ENABLED_NOT.value -> tintVector(context, R.drawable.ic_check_box_x_24dp) | ||||
|                     else -> throw Exception("Unknown state") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Interface for an item belonging to a group. | ||||
|      */ | ||||
|     interface GroupedItem { | ||||
|         val group: Group | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * A group containing a list of items. | ||||
|      */ | ||||
|     interface Group { | ||||
|  | ||||
|         /** | ||||
|          * An optional header for the group, typically a [Item.Header]. | ||||
|          */ | ||||
|         val header: Item? | ||||
|  | ||||
|         /** | ||||
|          * An optional footer for the group, typically a [Item.Separator]. | ||||
|          */ | ||||
|         val footer: Item? | ||||
|  | ||||
|         /** | ||||
|          * The items of the group, excluding header and footer. | ||||
|          */ | ||||
|         val items: List<Item> | ||||
|  | ||||
|         /** | ||||
|          * Creates all the elements of this group. Implementations can override this method for more | ||||
|          * customization. | ||||
|          */ | ||||
|         fun createItems() = (mutableListOf<Item>() + header + items + footer).filterNotNull() | ||||
|  | ||||
|         /** | ||||
|          * Called after creating the list of items. Implementations should load the current values | ||||
|          * into the models. | ||||
|          */ | ||||
|         fun initModels() | ||||
|  | ||||
|         /** | ||||
|          * Called when an item of this group is clicked. The group is responsible for all the | ||||
|          * selections of its items. | ||||
|          */ | ||||
|         fun onItemClicked(item: Item) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Base adapter for the navigation view. It knows how to create and render every subclass of | ||||
|      * [Item]. | ||||
|      */ | ||||
|     abstract inner class Adapter(private val items: List<Item>) : RecyclerView.Adapter<Holder>() { | ||||
|  | ||||
|         private val onClick = OnClickListener { | ||||
|             val pos = recycler.getChildAdapterPosition(it) | ||||
|             val item = items[pos] | ||||
|             onItemClicked(item) | ||||
|         } | ||||
|  | ||||
|         fun notifyItemChanged(item: Item) { | ||||
|             val pos = items.indexOf(item) | ||||
|             if (pos != -1) notifyItemChanged(pos) | ||||
|         } | ||||
|  | ||||
|         override fun getItemCount(): Int { | ||||
|             return items.size | ||||
|         } | ||||
|  | ||||
|         @CallSuper | ||||
|         override fun getItemViewType(position: Int): Int { | ||||
|             return when (items[position]) { | ||||
|                 is Item.Header -> VIEW_TYPE_HEADER | ||||
|                 is Item.Separator -> VIEW_TYPE_SEPARATOR | ||||
|                 is Item.Radio -> VIEW_TYPE_RADIO | ||||
|                 is Item.Checkbox -> VIEW_TYPE_CHECKBOX | ||||
|                 is Item.MultiState -> VIEW_TYPE_MULTISTATE | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         @CallSuper | ||||
|         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { | ||||
|             return when (viewType) { | ||||
|                 VIEW_TYPE_HEADER -> HeaderHolder(parent) | ||||
|                 VIEW_TYPE_SEPARATOR -> SeparatorHolder(parent) | ||||
|                 VIEW_TYPE_RADIO -> RadioHolder(parent, onClick) | ||||
|                 VIEW_TYPE_CHECKBOX -> CheckboxHolder(parent, onClick) | ||||
|                 VIEW_TYPE_MULTISTATE -> MultiStateHolder(parent, onClick) | ||||
|                 else -> throw Exception("Unknown view type") | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         @CallSuper | ||||
|         override fun onBindViewHolder(holder: Holder, position: Int) { | ||||
|             when (holder) { | ||||
|                 is HeaderHolder -> { | ||||
|                     val item = items[position] as Item.Header | ||||
|                     holder.title.setText(item.resTitle) | ||||
|                 } | ||||
|                 is SeparatorHolder -> { | ||||
|                     val view = holder.itemView | ||||
|                     val item = items[position] as Item.Separator | ||||
|                     view.updatePadding(top = item.paddingTop, bottom = item.paddingBottom) | ||||
|                 } | ||||
|                 is RadioHolder -> { | ||||
|                     val item = items[position] as Item.Radio | ||||
|                     holder.radio.setText(item.resTitle) | ||||
|                     holder.radio.isChecked = item.checked | ||||
|  | ||||
|                     holder.itemView.isClickable = item.enabled | ||||
|                     holder.radio.isEnabled = item.enabled | ||||
|                 } | ||||
|                 is CheckboxHolder -> { | ||||
|                     val item = items[position] as Item.CheckboxGroup | ||||
|                     holder.check.setText(item.resTitle) | ||||
|                     holder.check.isChecked = item.checked | ||||
|  | ||||
|                     holder.itemView.isClickable = item.enabled | ||||
|                     holder.check.isEnabled = item.enabled | ||||
|                 } | ||||
|                 is MultiStateHolder -> { | ||||
|                     val item = items[position] as Item.MultiStateGroup | ||||
|                     val drawable = item.getStateDrawable(context) | ||||
|                     holder.text.setText(item.resTitle) | ||||
|                     holder.text.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) | ||||
|  | ||||
|                     holder.itemView.isClickable = item.enabled | ||||
|                     holder.text.isEnabled = item.enabled | ||||
|  | ||||
|                     // Mimics checkbox/radio button | ||||
|                     holder.text.alpha = if (item.enabled) 1f else 0.4f | ||||
|                     holder.itemView.isVisible = item.isVisible | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         abstract fun onItemClicked(item: Item) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								app/src/main/java/eu/kanade/tachiyomi/widget/TriState.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/src/main/java/eu/kanade/tachiyomi/widget/TriState.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| package eu.kanade.tachiyomi.widget | ||||
|  | ||||
| import tachiyomi.domain.manga.model.TriStateFilter | ||||
|  | ||||
| // TODO: replace this with TriStateFilter entirely | ||||
| enum class TriState(val value: Int) { | ||||
|     DISABLED(0), | ||||
|     ENABLED_IS(1), | ||||
|     ENABLED_NOT(2), | ||||
| } | ||||
|  | ||||
| fun Int.toTriStateFilter(): TriStateFilter { | ||||
|     return when (this) { | ||||
|         TriState.DISABLED.value -> TriStateFilter.DISABLED | ||||
|         TriState.ENABLED_IS.value -> TriStateFilter.ENABLED_IS | ||||
|         TriState.ENABLED_NOT.value -> TriStateFilter.ENABLED_NOT | ||||
|         else -> throw IllegalStateException("Unknown TriStateGroup state: $this") | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user