Library filtering and sorting modes

This commit is contained in:
Syer10
2022-04-06 20:41:48 -04:00
parent 0413ab5b42
commit e11eed95d7
24 changed files with 957 additions and 96 deletions

View File

@@ -9,12 +9,25 @@ package ca.gosyer.jui.data.library
import ca.gosyer.jui.core.prefs.Preference
import ca.gosyer.jui.core.prefs.PreferenceStore
import ca.gosyer.jui.data.library.model.DisplayMode
import ca.gosyer.jui.data.library.model.FilterState
import ca.gosyer.jui.data.library.model.Sort
class LibraryPreferences(private val preferenceStore: PreferenceStore) {
fun displayMode(): Preference<DisplayMode> {
return preferenceStore.getJsonObject("display_mode", DisplayMode.CompactGrid, DisplayMode.serializer())
fun showAllCategory(): Preference<Boolean> {
return preferenceStore.getBoolean("show_all_category", false)
}
fun filterDownloaded(): Preference<FilterState> {
return preferenceStore.getJsonObject("filter_downloaded", FilterState.IGNORED, FilterState.serializer())
}
fun filterUnread(): Preference<FilterState> {
return preferenceStore.getJsonObject("filter_unread", FilterState.IGNORED, FilterState.serializer())
}
fun filterCompleted(): Preference<FilterState> {
return preferenceStore.getJsonObject("filter_completed", FilterState.IGNORED, FilterState.serializer())
}
fun sortMode(): Preference<Sort> {
@@ -25,6 +38,10 @@ class LibraryPreferences(private val preferenceStore: PreferenceStore) {
return preferenceStore.getBoolean("sort_ascending", true)
}
fun displayMode(): Preference<DisplayMode> {
return preferenceStore.getJsonObject("display_mode", DisplayMode.CompactGrid, DisplayMode.serializer())
}
fun gridColumns(): Preference<Int> {
return preferenceStore.getInt("grid_columns", 0)
}
@@ -33,7 +50,19 @@ class LibraryPreferences(private val preferenceStore: PreferenceStore) {
return preferenceStore.getInt("grid_size", 160)
}
fun showAllCategory(): Preference<Boolean> {
return preferenceStore.getBoolean("show_all_category", false)
fun unreadBadge(): Preference<Boolean> {
return preferenceStore.getBoolean("unread_badge", true)
}
fun downloadBadge(): Preference<Boolean> {
return preferenceStore.getBoolean("download_badge", false)
}
fun languageBadge(): Preference<Boolean> {
return preferenceStore.getBoolean("language_badge", false)
}
fun localBadge(): Preference<Boolean> {
return preferenceStore.getBoolean("local_badge", false)
}
}

View File

@@ -19,6 +19,6 @@ enum class DisplayMode(@Transient val res: StringResource) {
List(MR.strings.display_list);
companion object {
val values = values()
val values = values().asList()
}
}

View File

@@ -0,0 +1,16 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.data.library.model
import kotlinx.serialization.Serializable
@Serializable
enum class FilterState {
IGNORED,
INCLUDED,
EXCLUDED
}

View File

@@ -6,18 +6,21 @@
package ca.gosyer.jui.data.library.model
import ca.gosyer.jui.i18n.MR
import dev.icerock.moko.resources.StringResource
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
enum class Sort {
ALPHABETICAL,
enum class Sort(@Transient val res: StringResource) {
ALPHABETICAL(MR.strings.sort_alphabetical),
// LAST_READ,
// LAST_CHECKED,
UNREAD,
UNREAD(MR.strings.sort_unread),
// TOTAL_CHAPTERS,
// LATEST_CHAPTER,
// DATE_FETCHED,
DATE_ADDED;
DATE_ADDED(MR.strings.sort_date_added);
}

View File

@@ -45,6 +45,7 @@
<string name="action_more_actions">More actions</string>
<string name="action_ok">Ok</string>
<string name="action_browser">Browser</string>
<string name="action_filter">Filter</string>
<!-- Locations -->
<string name="location_library">Library</string>
@@ -81,6 +82,23 @@
<string name="default_category">Default</string>
<string name="library_empty">Library is empty</string>
<string name="library_sort">Sort</string>
<string name="library_display">Display</string>
<string name="filter_downloaded">Downloaded</string>
<string name="filter_unread">Unread</string>
<string name="filter_completed">Completed</string>
<string name="sort_alphabetical">Alphabetically</string>
<string name="sort_unread">Unread</string>
<string name="sort_date_added">Date added</string>
<string name="display_badges">Badges</string>
<string name="display_badge_downloaded">Downloaded chapters</string>
<string name="display_badge_unread">Unread chapters</string>
<string name="display_badge_local">Local manga</string>
<string name="display_badge_language">Language</string>
<!-- Manga Menu -->
<string name="page_progress">Page %1$d</string>
<string name="no_chapters_found">No chapters found</string>
@@ -100,7 +118,6 @@
<string name="move_to_browse">Browse</string>
<string name="move_to_latest">Latest</string>
<string name="reset_filters">Reset</string>
<string name="filter_source">Filter</string>
<string name="in_library">In library</string>
<string name="no_results_found">No results found</string>

View File

@@ -11,6 +11,7 @@ import ca.gosyer.jui.ui.categories.CategoriesScreenViewModel
import ca.gosyer.jui.ui.downloads.DownloadsScreenViewModel
import ca.gosyer.jui.ui.extensions.ExtensionsScreenViewModel
import ca.gosyer.jui.ui.library.LibraryScreenViewModel
import ca.gosyer.jui.ui.library.settings.LibrarySettingsViewModel
import ca.gosyer.jui.ui.main.MainViewModel
import ca.gosyer.jui.ui.main.about.AboutViewModel
import ca.gosyer.jui.ui.main.components.DebugOverlayViewModel
@@ -43,6 +44,7 @@ actual class ViewModelFactoryImpl(
private val downloadsFactory: (Boolean) -> DownloadsScreenViewModel,
private val extensionsFactory: () -> ExtensionsScreenViewModel,
private val libraryFactory: () -> LibraryScreenViewModel,
private val librarySettingsFactory: () -> LibrarySettingsViewModel,
private val debugOverlayFactory: () -> DebugOverlayViewModel,
private val mainFactory: () -> MainViewModel,
private val mangaFactory: (params: MangaScreenViewModel.Params) -> MangaScreenViewModel,
@@ -72,6 +74,7 @@ actual class ViewModelFactoryImpl(
DownloadsScreenViewModel::class -> downloadsFactory(arg1 as Boolean)
ExtensionsScreenViewModel::class -> extensionsFactory()
LibraryScreenViewModel::class -> libraryFactory()
LibrarySettingsViewModel::class -> librarySettingsFactory()
DebugOverlayViewModel::class -> debugOverlayFactory()
MainViewModel::class -> mainFactory()
MangaScreenViewModel::class -> mangaFactory(arg1 as MangaScreenViewModel.Params)

View File

@@ -9,6 +9,10 @@ package ca.gosyer.jui.ui.library
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import ca.gosyer.jui.ui.library.components.LibraryScreenContent
import ca.gosyer.jui.ui.library.settings.LibrarySettingsViewModel
import ca.gosyer.jui.ui.library.settings.getLibraryDisplay
import ca.gosyer.jui.ui.library.settings.getLibraryFilters
import ca.gosyer.jui.ui.library.settings.getLibrarySort
import ca.gosyer.jui.ui.manga.MangaScreen
import ca.gosyer.jui.uicore.vm.viewModel
import cafe.adriel.voyager.core.screen.Screen
@@ -24,6 +28,7 @@ class LibraryScreen : Screen {
@Composable
override fun Content() {
val vm = viewModel<LibraryScreenViewModel>()
val settingsVM = viewModel<LibrarySettingsViewModel>()
val navigator = LocalNavigator.currentOrThrow
LibraryScreenContent(
categories = vm.categories.collectAsState().value,
@@ -38,7 +43,16 @@ class LibraryScreen : Screen {
getLibraryForPage = { vm.getLibraryForCategoryId(it).collectAsState() },
onPageChanged = vm::setSelectedPage,
onClickManga = { navigator push MangaScreen(it) },
onRemoveMangaClicked = vm::removeManga
onRemoveMangaClicked = vm::removeManga,
showingMenu = vm.showingMenu.collectAsState().value,
setShowingMenu = vm::setShowingMenu,
libraryFilters = getLibraryFilters(settingsVM),
librarySort = getLibrarySort(settingsVM),
libraryDisplay = getLibraryDisplay(settingsVM),
showUnread = vm.unreadBadges.collectAsState().value,
showDownloaded = vm.downloadBadges.collectAsState().value,
showLanguage = vm.languageBadges.collectAsState().value,
showLocal = vm.localBadges.collectAsState().value
)
}
}

View File

@@ -9,10 +9,13 @@ package ca.gosyer.jui.ui.library
import ca.gosyer.jui.core.lang.getDefault
import ca.gosyer.jui.core.lang.lowercase
import ca.gosyer.jui.core.lang.withDefaultContext
import ca.gosyer.jui.core.prefs.getAsFlow
import ca.gosyer.jui.data.library.LibraryPreferences
import ca.gosyer.jui.data.library.model.FilterState
import ca.gosyer.jui.data.library.model.Sort
import ca.gosyer.jui.data.models.Category
import ca.gosyer.jui.data.models.Manga
import ca.gosyer.jui.data.models.MangaStatus
import ca.gosyer.jui.data.server.interactions.CategoryInteractionHandler
import ca.gosyer.jui.data.server.interactions.LibraryInteractionHandler
import ca.gosyer.jui.data.server.interactions.UpdatesInteractionHandler
@@ -67,13 +70,42 @@ class LibraryScreenViewModel @Inject constructor(
private val _selectedCategoryIndex = MutableStateFlow(0)
val selectedCategoryIndex = _selectedCategoryIndex.asStateFlow()
private val _showingMenu = MutableStateFlow(false)
val showingMenu = _showingMenu.asStateFlow()
val displayMode = libraryPreferences.displayMode().stateIn(scope)
val gridColumns = libraryPreferences.gridColumns().stateIn(scope)
val gridSize = libraryPreferences.gridSize().stateIn(scope)
val unreadBadges = libraryPreferences.unreadBadge().stateIn(scope)
val downloadBadges = libraryPreferences.downloadBadge().stateIn(scope)
val languageBadges = libraryPreferences.languageBadge().stateIn(scope)
val localBadges = libraryPreferences.localBadge().stateIn(scope)
private val sortMode = libraryPreferences.sortMode().stateIn(scope)
private val sortAscending = libraryPreferences.sortAscending().stateIn(scope)
private val filter = combine(
libraryPreferences.filterDownloaded().getAsFlow(),
libraryPreferences.filterUnread().getAsFlow(),
libraryPreferences.filterCompleted().getAsFlow()
) { downloaded, unread, completed ->
{ manga: Manga ->
when (downloaded) {
FilterState.EXCLUDED -> manga.downloadCount == null || manga.downloadCount == 0
FilterState.INCLUDED -> manga.downloadCount != null && (manga.downloadCount ?: 0) > 0
FilterState.IGNORED -> true
} && when (unread) {
FilterState.EXCLUDED -> manga.unreadCount == null || manga.unreadCount == 0
FilterState.INCLUDED -> manga.unreadCount != null && (manga.unreadCount ?: 0) > 0
FilterState.IGNORED -> true
} && when (completed) {
FilterState.EXCLUDED -> manga.status != MangaStatus.COMPLETED
FilterState.INCLUDED -> manga.status == MangaStatus.COMPLETED
FilterState.IGNORED -> true
}
}
}
private val _isLoading = MutableStateFlow(true)
val isLoading = _isLoading.asStateFlow()
@@ -114,6 +146,10 @@ class LibraryScreenViewModel @Inject constructor(
_selectedCategoryIndex.value = page
}
fun setShowingMenu(showingMenu: Boolean) {
_showingMenu.value = showingMenu
}
private fun getComparator(sortMode: Sort, ascending: Boolean): Comparator<Manga> {
val sortFn = when (sortMode) {
Sort.ALPHABETICAL -> {
@@ -164,6 +200,8 @@ class LibraryScreenViewModel @Inject constructor(
private fun getMangaItemsFlow(unfilteredItemsFlow: StateFlow<List<Manga>>): StateFlow<List<Manga>> {
return combine(unfilteredItemsFlow, query) { unfilteredItems, query ->
filterManga(query, unfilteredItems)
}.combine(filter) { filteredManga, filterer ->
filteredManga.filter(filterer)
}.combine(comparator) { filteredManga, comparator ->
filteredManga.sortedWith(comparator)
}.stateIn(scope, SharingStarted.Eagerly, emptyList())

View File

@@ -7,7 +7,10 @@
package ca.gosyer.jui.ui.library.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
@@ -15,33 +18,66 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import ca.gosyer.jui.data.models.Manga
import ca.gosyer.jui.data.models.Source
@Composable
fun LibraryMangaBadges(
unread: Int?,
downloaded: Int?,
modifier: Modifier = Modifier,
manga: Manga,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
if (unread == null && downloaded == null) return
val unread = manga.unreadCount
val downloaded = manga.downloadCount
val isLocal = manga.sourceId == Source.LOCAL_SOURCE_ID
Row(modifier = modifier.clip(MaterialTheme.shapes.medium)) {
if (unread != null && unread > 0) {
Text(
text = unread.toString(),
modifier = Modifier.background(MaterialTheme.colors.primary).then(BadgesInnerPadding),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onPrimary
)
Row(modifier then Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
if ((unread != null && unread > 0) || (downloaded != null && downloaded > 0) || isLocal) {
Row(modifier = Modifier.clip(MaterialTheme.shapes.medium)) {
if (showLocal && isLocal) {
Text(
text = unread.toString(),
modifier = Modifier.background(MaterialTheme.colors.secondary).then(BadgesInnerPadding),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onSecondary
)
}
if (showUnread && unread != null && unread > 0) {
Text(
text = unread.toString(),
modifier = Modifier.background(MaterialTheme.colors.primary).then(BadgesInnerPadding),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onPrimary
)
}
if (showDownloaded && downloaded != null && downloaded > 0) {
Text(
text = downloaded.toString(),
modifier = Modifier.background(MaterialTheme.colors.secondary).then(
BadgesInnerPadding
),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onSecondary
)
}
}
} else {
Spacer(Modifier)
}
if (downloaded != null && downloaded > 0) {
Text(
text = downloaded.toString(),
modifier = Modifier.background(MaterialTheme.colors.secondary).then(
BadgesInnerPadding
),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onSecondary
)
val lang = manga.source?.lang
if (showLanguage && lang != null) {
Row(modifier = Modifier.clip(MaterialTheme.shapes.medium)) {
Text(
text = lang.uppercase(),
modifier = Modifier.background(MaterialTheme.colors.secondary).then(BadgesInnerPadding),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onSecondary
)
}
}
}
}

View File

@@ -7,11 +7,13 @@
package ca.gosyer.jui.ui.library.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
@@ -34,7 +36,11 @@ import io.kamel.image.lazyPainterResource
fun LibraryMangaList(
library: List<Manga>,
onClickManga: (Long) -> Unit,
onRemoveMangaClicked: (Long) -> Unit
onRemoveMangaClicked: (Long) -> Unit,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
Box {
val state = rememberLazyListState()
@@ -49,8 +55,10 @@ fun LibraryMangaList(
{ onRemoveMangaClicked(manga.id) }
),
manga = manga,
unread = manga.unreadCount,
downloaded = manga.downloadCount,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
}
}
@@ -67,8 +75,10 @@ fun LibraryMangaList(
private fun LibraryMangaListItem(
modifier: Modifier,
manga: Manga,
unread: Int?,
downloaded: Int?,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
MangaListItem(
@@ -89,6 +99,14 @@ private fun LibraryMangaListItem(
.padding(horizontal = 16.dp),
text = manga.title,
)
LibraryMangaBadges(unread, downloaded)
Box(Modifier.width(IntrinsicSize.Min)) {
LibraryMangaBadges(
manga = manga,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
}
}
}

View File

@@ -25,7 +25,11 @@ fun LibraryPager(
gridSize: Int,
getLibraryForPage: @Composable (Long) -> State<List<Manga>>,
onClickManga: (Long) -> Unit,
onRemoveMangaClicked: (Long) -> Unit
onRemoveMangaClicked: (Long) -> Unit,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
if (categories.isEmpty()) return
@@ -37,26 +41,42 @@ fun LibraryPager(
gridColumns = gridColumns,
gridSize = gridSize,
onClickManga = onClickManga,
onRemoveMangaClicked = onRemoveMangaClicked
onRemoveMangaClicked = onRemoveMangaClicked,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
DisplayMode.ComfortableGrid -> LibraryMangaComfortableGrid(
library = library,
gridColumns = gridColumns,
gridSize = gridSize,
onClickManga = onClickManga,
onRemoveMangaClicked = onRemoveMangaClicked
onRemoveMangaClicked = onRemoveMangaClicked,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
DisplayMode.CoverOnlyGrid -> LibraryMangaCoverOnlyGrid(
library = library,
gridColumns = gridColumns,
gridSize = gridSize,
onClickManga = onClickManga,
onRemoveMangaClicked = onRemoveMangaClicked
onRemoveMangaClicked = onRemoveMangaClicked,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
DisplayMode.List -> LibraryMangaList(
library = library,
onClickManga = onClickManga,
onRemoveMangaClicked = onRemoveMangaClicked
onRemoveMangaClicked = onRemoveMangaClicked,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
else -> Box {}
}

View File

@@ -6,23 +6,40 @@
package ca.gosyer.jui.ui.library.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.BottomSheetScaffold
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.Scaffold
import androidx.compose.material.rememberBottomSheetScaffoldState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.FilterList
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import ca.gosyer.jui.data.library.model.DisplayMode
import ca.gosyer.jui.data.models.Category
import ca.gosyer.jui.data.models.Manga
import ca.gosyer.jui.i18n.MR
import ca.gosyer.jui.ui.base.navigation.ActionItem
import ca.gosyer.jui.ui.base.navigation.Toolbar
import ca.gosyer.jui.ui.library.settings.LibrarySheet
import ca.gosyer.jui.ui.library.settings.LibrarySideMenu
import ca.gosyer.jui.uicore.components.LoadingScreen
import ca.gosyer.jui.uicore.resources.stringResource
import com.google.accompanist.pager.PagerState
@@ -42,7 +59,16 @@ fun LibraryScreenContent(
getLibraryForPage: @Composable (Long) -> State<List<Manga>>,
onPageChanged: (Int) -> Unit,
onClickManga: (Long) -> Unit,
onRemoveMangaClicked: (Long) -> Unit
onRemoveMangaClicked: (Long) -> Unit,
showingMenu: Boolean,
setShowingMenu: (Boolean) -> Unit,
libraryFilters: @Composable () -> Unit,
librarySort: @Composable () -> Unit,
libraryDisplay: @Composable () -> Unit,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
BoxWithConstraints {
val pagerState = rememberPagerState(selectedCategoryIndex)
@@ -71,7 +97,16 @@ fun LibraryScreenContent(
getLibraryForPage = getLibraryForPage,
onPageChanged = onPageChanged,
onClickManga = onClickManga,
onRemoveMangaClicked = onRemoveMangaClicked
onRemoveMangaClicked = onRemoveMangaClicked,
showingMenu = showingMenu,
setShowingMenu = setShowingMenu,
libraryFilters = libraryFilters,
librarySort = librarySort,
libraryDisplay = libraryDisplay,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
} else {
ThinLibraryScreenContent(
@@ -88,7 +123,16 @@ fun LibraryScreenContent(
getLibraryForPage = getLibraryForPage,
onPageChanged = onPageChanged,
onClickManga = onClickManga,
onRemoveMangaClicked = onRemoveMangaClicked
onRemoveMangaClicked = onRemoveMangaClicked,
showingSheet = showingMenu,
setShowingSheet = setShowingMenu,
libraryFilters = libraryFilters,
librarySort = librarySort,
libraryDisplay = libraryDisplay,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
}
}
@@ -109,7 +153,16 @@ fun WideLibraryScreenContent(
getLibraryForPage: @Composable (Long) -> State<List<Manga>>,
onPageChanged: (Int) -> Unit,
onClickManga: (Long) -> Unit,
onRemoveMangaClicked: (Long) -> Unit
onRemoveMangaClicked: (Long) -> Unit,
showingMenu: Boolean,
setShowingMenu: (Boolean) -> Unit,
libraryFilters: @Composable () -> Unit,
librarySort: @Composable () -> Unit,
libraryDisplay: @Composable () -> Unit,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
Scaffold(
topBar = {
@@ -117,7 +170,12 @@ fun WideLibraryScreenContent(
Toolbar(
stringResource(MR.strings.location_library),
searchText = query,
search = updateQuery
search = updateQuery,
actions = {
getActionItems(
onToggleFiltersClick = { setShowingMenu(true) }
)
}
)
LibraryTabs(
visible = true, // vm.showCategoryTabs,
@@ -128,8 +186,8 @@ fun WideLibraryScreenContent(
)
}
}
) {
Box(Modifier.padding(it)) {
) { padding ->
Box(Modifier.padding(padding)) {
if (categories.isEmpty()) {
LoadingScreen(isLoading, errorMessage = error)
} else {
@@ -141,8 +199,36 @@ fun WideLibraryScreenContent(
gridSize = gridSize,
getLibraryForPage = getLibraryForPage,
onClickManga = onClickManga,
onRemoveMangaClicked = onRemoveMangaClicked
onRemoveMangaClicked = onRemoveMangaClicked,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
if (showingMenu) {
Box(
Modifier.fillMaxSize().pointerInput(isLoading) {
forEachGesture {
detectTapGestures {
setShowingMenu(false)
}
}
}
)
}
AnimatedVisibility(
showingMenu,
enter = fadeIn() + slideInHorizontally(initialOffsetX = { it * 2 }),
exit = fadeOut() + slideOutHorizontally(targetOffsetX = { it * 2 }),
modifier = Modifier.align(Alignment.TopEnd)
) {
LibrarySideMenu(
libraryFilters = libraryFilters,
librarySort = librarySort,
libraryDisplay = libraryDisplay
)
}
}
}
}
@@ -163,17 +249,47 @@ fun ThinLibraryScreenContent(
getLibraryForPage: @Composable (Long) -> State<List<Manga>>,
onPageChanged: (Int) -> Unit,
onClickManga: (Long) -> Unit,
onRemoveMangaClicked: (Long) -> Unit
onRemoveMangaClicked: (Long) -> Unit,
showingSheet: Boolean,
setShowingSheet: (Boolean) -> Unit,
libraryFilters: @Composable () -> Unit,
librarySort: @Composable () -> Unit,
libraryDisplay: @Composable () -> Unit,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
val sheetState = rememberBottomSheetScaffoldState()
BottomSheetScaffold(
scaffoldState = sheetState,
val bottomSheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden,
confirmStateChange = {
when (it) {
ModalBottomSheetValue.Hidden -> setShowingSheet(false)
ModalBottomSheetValue.Expanded,
ModalBottomSheetValue.HalfExpanded -> setShowingSheet(true)
}
true
}
)
LaunchedEffect(showingSheet) {
if (showingSheet) {
bottomSheetState.show()
} else {
bottomSheetState.hide()
}
}
Scaffold(
topBar = {
Column {
Toolbar(
stringResource(MR.strings.location_library),
searchText = query,
search = updateQuery
search = updateQuery,
actions = {
getActionItems(
onToggleFiltersClick = { setShowingSheet(true) }
)
}
)
LibraryTabs(
visible = true, // vm.showCategoryTabs,
@@ -183,13 +299,19 @@ fun ThinLibraryScreenContent(
onPageChanged = onPageChanged
)
}
},
sheetContent = {
// LibrarySheetContent()
},
sheetPeekHeight = 0.dp
) {
Box(Modifier.padding(it)) {
}
) { padding ->
ModalBottomSheetLayout(
sheetState = bottomSheetState,
modifier = Modifier.padding(padding),
sheetContent = {
LibrarySheet(
libraryFilters = libraryFilters,
librarySort = librarySort,
libraryDisplay = libraryDisplay
)
}
) {
if (categories.isEmpty()) {
LoadingScreen(isLoading, errorMessage = error)
} else {
@@ -201,9 +323,27 @@ fun ThinLibraryScreenContent(
gridSize = gridSize,
getLibraryForPage = getLibraryForPage,
onClickManga = onClickManga,
onRemoveMangaClicked = onRemoveMangaClicked
onRemoveMangaClicked = onRemoveMangaClicked,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
}
}
}
}
@Composable
@Stable
private fun getActionItems(
onToggleFiltersClick: () -> Unit,
): List<ActionItem> {
return listOfNotNull(
ActionItem(
name = stringResource(MR.strings.action_filter),
icon = Icons.Rounded.FilterList,
doAction = onToggleFiltersClick
)
)
}

View File

@@ -43,7 +43,11 @@ fun LibraryMangaComfortableGrid(
gridColumns: Int,
gridSize: Int,
onClickManga: (Long) -> Unit,
onRemoveMangaClicked: (Long) -> Unit
onRemoveMangaClicked: (Long) -> Unit,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
Box {
val state = rememberLazyListState()
@@ -64,8 +68,10 @@ fun LibraryMangaComfortableGrid(
{ onRemoveMangaClicked(manga.id) }
),
manga = manga,
unread = manga.unreadCount,
downloaded = manga.downloadCount
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
}
}
@@ -82,8 +88,10 @@ fun LibraryMangaComfortableGrid(
private fun LibraryMangaComfortableGridItem(
modifier: Modifier,
manga: Manga,
unread: Int?,
downloaded: Int?
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge(
@@ -114,9 +122,12 @@ private fun LibraryMangaComfortableGridItem(
)
}
LibraryMangaBadges(
unread = unread,
downloaded = downloaded,
modifier = Modifier.padding(4.dp)
modifier = Modifier.padding(4.dp),
manga = manga,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
}
}

View File

@@ -51,7 +51,11 @@ fun LibraryMangaCompactGrid(
gridColumns: Int,
gridSize: Int,
onClickManga: (Long) -> Unit,
onRemoveMangaClicked: (Long) -> Unit
onRemoveMangaClicked: (Long) -> Unit,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
Box {
val state = rememberLazyListState()
@@ -72,8 +76,10 @@ fun LibraryMangaCompactGrid(
{ onRemoveMangaClicked(manga.id) }
),
manga = manga,
unread = manga.unreadCount,
downloaded = manga.downloadCount
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
}
}
@@ -90,8 +96,10 @@ fun LibraryMangaCompactGrid(
private fun LibraryMangaCompactGridItem(
modifier: Modifier,
manga: Manga,
unread: Int?,
downloaded: Int?,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge(
@@ -119,9 +127,12 @@ private fun LibraryMangaCompactGridItem(
modifier = Modifier.align(Alignment.BottomStart).padding(8.dp)
)
LibraryMangaBadges(
unread = unread,
downloaded = downloaded,
modifier = Modifier.padding(4.dp)
modifier = Modifier.padding(4.dp),
manga = manga,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
}
}

View File

@@ -37,7 +37,11 @@ fun LibraryMangaCoverOnlyGrid(
gridColumns: Int,
gridSize: Int,
onClickManga: (Long) -> Unit,
onRemoveMangaClicked: (Long) -> Unit
onRemoveMangaClicked: (Long) -> Unit,
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
Box {
val state = rememberLazyListState()
@@ -58,8 +62,10 @@ fun LibraryMangaCoverOnlyGrid(
{ onRemoveMangaClicked(manga.id) }
),
manga = manga,
unread = manga.unreadCount,
downloaded = manga.downloadCount
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
}
}
@@ -76,8 +82,10 @@ fun LibraryMangaCoverOnlyGrid(
private fun LibraryMangaCoverOnlyGridItem(
modifier: Modifier,
manga: Manga,
unread: Int?,
downloaded: Int?
showUnread: Boolean,
showDownloaded: Boolean,
showLanguage: Boolean,
showLocal: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
@@ -94,9 +102,12 @@ private fun LibraryMangaCoverOnlyGridItem(
contentScale = ContentScale.Crop
)
LibraryMangaBadges(
unread = unread,
downloaded = downloaded,
modifier = Modifier.padding(4.dp)
modifier = Modifier.padding(4.dp),
manga = manga,
showUnread = showUnread,
showDownloaded = showDownloaded,
showLanguage = showLanguage,
showLocal = showLocal
)
}
}

View File

@@ -0,0 +1,127 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.ui.library.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Checkbox
import androidx.compose.material.RadioButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import ca.gosyer.jui.data.library.model.DisplayMode
import ca.gosyer.jui.i18n.MR
import ca.gosyer.jui.ui.sources.browse.filter.SourceFilterAction
import ca.gosyer.jui.uicore.resources.stringResource
@Composable
fun getLibraryDisplay(vm: LibrarySettingsViewModel): @Composable () -> Unit = remember(vm) {
@Composable {
LibraryDisplay(
displayMode = vm.displayMode.collectAsState().value,
unreadBadges = vm.unreadBadges.collectAsState().value,
downloadBadges = vm.downloadBadges.collectAsState().value,
languageBadges = vm.languageBadges.collectAsState().value,
localBadges = vm.localBadges.collectAsState().value,
setDisplayMode = { vm.displayMode.value = it },
setUnreadBadges = { vm.unreadBadges.value = it },
setDownloadBadges = { vm.downloadBadges.value = it },
setLanguageBadges = { vm.languageBadges.value = it },
setLocalBadges = { vm.localBadges.value = it }
)
}
}
@Composable
fun LibraryDisplay(
displayMode: DisplayMode,
unreadBadges: Boolean,
downloadBadges: Boolean,
languageBadges: Boolean,
localBadges: Boolean,
setDisplayMode: (DisplayMode) -> Unit,
setUnreadBadges: (Boolean) -> Unit,
setDownloadBadges: (Boolean) -> Unit,
setLanguageBadges: (Boolean) -> Unit,
setLocalBadges: (Boolean) -> Unit
) {
Column(Modifier.fillMaxWidth()) {
TitleText(stringResource(MR.strings.display_mode))
DisplayMode.values.fastForEach {
RadioSelectionItem(
text = stringResource(it.res),
selected = it == displayMode,
onClick = { setDisplayMode(it) }
)
}
TitleText(stringResource(MR.strings.display_badges))
CheckboxItem(
text = stringResource(MR.strings.display_badge_downloaded),
checked = downloadBadges,
onClick = { setDownloadBadges(!downloadBadges) }
)
CheckboxItem(
text = stringResource(MR.strings.display_badge_unread),
checked = unreadBadges,
onClick = { setUnreadBadges(!unreadBadges) }
)
CheckboxItem(
text = stringResource(MR.strings.display_badge_local),
checked = localBadges,
onClick = { setLocalBadges(!localBadges) }
)
// TODO: 2022-04-06 Enable when library contains manga source in manga object
/*CheckboxItem(
text = stringResource(MR.strings.display_badge_language),
checked = languageBadges,
onClick = { setLanguageBadges(!languageBadges) }
)*/
}
}
@Composable
private fun TitleText(text: String) {
Text(
text = text,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
@Composable
private fun RadioSelectionItem(text: String, selected: Boolean, onClick: () -> Unit) {
SourceFilterAction(
name = text,
onClick = onClick,
action = {
RadioButton(
selected = selected,
onClick = null
)
}
)
}
@Composable
private fun CheckboxItem(text: String, checked: Boolean, onClick: () -> Unit) {
SourceFilterAction(
name = text,
onClick = onClick,
action = {
Checkbox(
checked = checked,
onCheckedChange = null
)
}
)
}

View File

@@ -0,0 +1,86 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.ui.library.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.TriStateCheckbox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.state.ToggleableState
import ca.gosyer.jui.data.library.model.FilterState
import ca.gosyer.jui.i18n.MR
import ca.gosyer.jui.ui.sources.browse.filter.SourceFilterAction
import ca.gosyer.jui.uicore.resources.stringResource
@Composable
fun getLibraryFilters(vm: LibrarySettingsViewModel): @Composable () -> Unit = remember(vm) {
@Composable {
LibraryFilters(
downloaded = vm.filterDownloaded.collectAsState().value,
unread = vm.filterUnread.collectAsState().value,
completed = vm.filterCompleted.collectAsState().value,
setDownloadedFilter = { vm.filterDownloaded.value = it },
setUnreadFilter = { vm.filterUnread.value = it },
setCompletedFilter = { vm.filterCompleted.value = it },
)
}
}
@Composable
fun LibraryFilters(
downloaded: FilterState,
unread: FilterState,
completed: FilterState,
setDownloadedFilter: (FilterState) -> Unit,
setUnreadFilter: (FilterState) -> Unit,
setCompletedFilter: (FilterState) -> Unit,
) {
Column(Modifier.fillMaxWidth()) {
Filter(
stringResource(MR.strings.filter_downloaded),
downloaded,
onClick = { setDownloadedFilter(toggleState(downloaded)) }
)
Filter(
stringResource(MR.strings.filter_unread),
unread,
onClick = { setUnreadFilter(toggleState(unread)) }
)
Filter(
stringResource(MR.strings.filter_completed),
completed,
onClick = { setCompletedFilter(toggleState(completed)) }
)
}
}
fun toggleState(filterState: FilterState) = when (filterState) {
FilterState.IGNORED -> FilterState.INCLUDED
FilterState.INCLUDED -> FilterState.EXCLUDED
FilterState.EXCLUDED -> FilterState.IGNORED
}
@Composable
private fun Filter(text: String, state: FilterState, onClick: () -> Unit) {
SourceFilterAction(
text,
onClick = onClick,
action = {
TriStateCheckbox(
state = when (state) {
FilterState.INCLUDED -> ToggleableState.On
FilterState.EXCLUDED -> ToggleableState.Indeterminate
FilterState.IGNORED -> ToggleableState.Off
},
onClick = null
)
}
)
}

View File

@@ -0,0 +1,30 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.ui.library.settings
import ca.gosyer.jui.data.library.LibraryPreferences
import ca.gosyer.jui.uicore.vm.ContextWrapper
import ca.gosyer.jui.uicore.vm.ViewModel
import me.tatarka.inject.annotations.Inject
class LibrarySettingsViewModel @Inject constructor(
libraryPreferences: LibraryPreferences,
contextWrapper: ContextWrapper
) : ViewModel(contextWrapper) {
val filterDownloaded = libraryPreferences.filterDownloaded().asStateFlow()
val filterUnread = libraryPreferences.filterUnread().asStateFlow()
val filterCompleted = libraryPreferences.filterCompleted().asStateFlow()
val sortMode = libraryPreferences.sortMode().asStateFlow()
val sortAscending = libraryPreferences.sortAscending().asStateFlow()
val displayMode = libraryPreferences.displayMode().asStateFlow()
val unreadBadges = libraryPreferences.unreadBadge().asStateFlow()
val downloadBadges = libraryPreferences.downloadBadge().asStateFlow()
val languageBadges = libraryPreferences.languageBadge().asStateFlow()
val localBadges = libraryPreferences.localBadge().asStateFlow()
}

View File

@@ -0,0 +1,98 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.ui.library.settings
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Tab
import androidx.compose.material.TabRow
import androidx.compose.material.TabRowDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import ca.gosyer.jui.i18n.MR
import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
import ca.gosyer.jui.uicore.resources.stringResource
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.pagerTabIndicatorOffset
import com.google.accompanist.pager.rememberPagerState
import dev.icerock.moko.resources.StringResource
import kotlinx.coroutines.launch
enum class LibrarySheetTabs(val res: StringResource) {
FILTERS(MR.strings.action_filter),
SORT(MR.strings.library_sort),
DISPLAY(MR.strings.library_display)
}
@Composable
fun LibrarySheet(
libraryFilters: @Composable () -> Unit,
librarySort: @Composable () -> Unit,
libraryDisplay: @Composable () -> Unit
) {
val pagerState = rememberPagerState()
val selectedPage = pagerState.currentPage
val scope = rememberCoroutineScope()
Column(Modifier.fillMaxSize()) {
TabRow(
selectedTabIndex = pagerState.currentPage,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.pagerTabIndicatorOffset(pagerState, tabPositions)
)
}
) {
LibrarySheetTabs.values().asList().fastForEachIndexed { index, tab ->
Tab(
selected = selectedPage == index,
onClick = {
scope.launch { pagerState.animateScrollToPage(index) }
},
text = { Text(stringResource(tab.res)) }
)
}
}
HorizontalPager(
count = LibrarySheetTabs.values().size,
state = pagerState,
verticalAlignment = Alignment.Top
) {
val scrollState = rememberScrollState()
Box {
Column(
Modifier.fillMaxWidth()
.verticalScroll(scrollState)
) {
when (it) {
LibrarySheetTabs.FILTERS.ordinal -> libraryFilters()
LibrarySheetTabs.SORT.ordinal -> librarySort()
LibrarySheetTabs.DISPLAY.ordinal -> libraryDisplay()
}
}
VerticalScrollbar(
rememberScrollbarAdapter(scrollState),
Modifier.align(Alignment.CenterEnd)
.fillMaxHeight()
.padding(horizontal = 4.dp, vertical = 8.dp)
)
}
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.ui.library.settings
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Divider
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import ca.gosyer.jui.i18n.MR
import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
import ca.gosyer.jui.uicore.resources.stringResource
@Composable
fun LibrarySideMenu(
libraryFilters: @Composable () -> Unit,
librarySort: @Composable () -> Unit,
libraryDisplay: @Composable () -> Unit,
) {
Surface(Modifier.fillMaxHeight().width(260.dp), elevation = 1.dp) {
Box {
val scrollState = rememberScrollState()
Column(
Modifier.fillMaxWidth()
.verticalScroll(scrollState)
) {
TitleText(stringResource(MR.strings.action_filter))
libraryFilters()
Divider()
TitleText(stringResource(MR.strings.library_sort))
librarySort()
Divider()
TitleText(stringResource(MR.strings.library_display))
libraryDisplay()
}
VerticalScrollbar(
rememberScrollbarAdapter(scrollState),
Modifier.align(Alignment.CenterEnd)
.fillMaxHeight()
.padding(horizontal = 4.dp, vertical = 8.dp)
)
}
}
}
@Composable
private fun TitleText(text: String) {
Box(Modifier.fillMaxWidth().padding(vertical = 8.dp), contentAlignment = Alignment.Center) {
Text(text, fontWeight = FontWeight.Bold, fontSize = 18.sp)
}
}

View File

@@ -0,0 +1,78 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.ui.library.settings
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowDownward
import androidx.compose.material.icons.rounded.ArrowUpward
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import ca.gosyer.jui.data.library.model.Sort
import ca.gosyer.jui.ui.sources.browse.filter.SourceFilterAction
import ca.gosyer.jui.uicore.resources.stringResource
@Composable
fun getLibrarySort(vm: LibrarySettingsViewModel): @Composable () -> Unit = remember(vm) {
@Composable {
LibrarySort(
mode = vm.sortMode.collectAsState().value,
ascending = vm.sortAscending.collectAsState().value,
setMode = {
vm.sortMode.value = it
vm.sortAscending.value = true
},
setAscending = { vm.sortAscending.value = it }
)
}
}
@Composable
fun LibrarySort(
mode: Sort,
ascending: Boolean,
setMode: (Sort) -> Unit,
setAscending: (Boolean) -> Unit
) {
Column(Modifier.fillMaxWidth()) {
Sort.values().asList().fastForEach { sort ->
SourceFilterAction(
name = stringResource(sort.res),
onClick = {
if (mode == sort) {
setAscending(!ascending)
} else {
setMode(sort)
}
},
action = {
if (mode == sort) {
Icon(
imageVector = when (ascending) {
true -> Icons.Rounded.ArrowUpward
false -> Icons.Rounded.ArrowDownward
},
contentDescription = stringResource(sort.res),
modifier = Modifier.fillMaxHeight()
)
} else {
Box(Modifier.size(24.dp))
}
}
)
}
}
}

View File

@@ -353,7 +353,7 @@ private fun SourceThinScreenContent(
if (showFilterButton && !isLatest) {
ExtendedFloatingActionButton(
text = {
Text(stringResource(MR.strings.filter_source))
Text(stringResource(MR.strings.action_filter))
},
onClick = {
setShowingFilters(true)
@@ -361,7 +361,7 @@ private fun SourceThinScreenContent(
icon = {
Icon(
Icons.Rounded.FilterList,
stringResource(MR.strings.filter_source)
stringResource(MR.strings.action_filter)
)
},
modifier = Modifier.align(Alignment.BottomEnd)
@@ -506,7 +506,7 @@ private fun getActionItems(
return listOfNotNull(
if (showFilterButton) {
ActionItem(
name = stringResource(MR.strings.filter_source),
name = stringResource(MR.strings.action_filter),
icon = Icons.Rounded.FilterList,
doAction = onToggleFiltersClick,
enabled = !isLatest

View File

@@ -15,6 +15,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
@@ -86,7 +87,7 @@ fun SourceFiltersMenu(
Text(stringResource(MR.strings.reset_filters))
}
Button(onSearchClicked) {
Text(stringResource(MR.strings.filter_source))
Text(stringResource(MR.strings.action_filter))
}
}
}
@@ -141,7 +142,8 @@ fun SourceFilterAction(
Row(
Modifier.fillMaxWidth().defaultMinSize(minHeight = 56.dp)
.clickable(onClick = onClick)
.padding(horizontal = 16.dp),
.padding(horizontal = 16.dp)
.height(IntrinsicSize.Min),
verticalAlignment = Alignment.CenterVertically
) {
action()

View File

@@ -11,10 +11,12 @@ import ca.gosyer.jui.ui.categories.CategoriesScreenViewModel
import ca.gosyer.jui.ui.downloads.DownloadsScreenViewModel
import ca.gosyer.jui.ui.extensions.ExtensionsScreenViewModel
import ca.gosyer.jui.ui.library.LibraryScreenViewModel
import ca.gosyer.jui.ui.library.settings.LibrarySettingsViewModel
import ca.gosyer.jui.ui.main.MainViewModel
import ca.gosyer.jui.ui.main.about.AboutViewModel
import ca.gosyer.jui.ui.main.components.DebugOverlayViewModel
import ca.gosyer.jui.ui.main.components.TrayViewModel
import ca.gosyer.jui.ui.manga.MangaScreenViewModel
import ca.gosyer.jui.ui.reader.ReaderMenuViewModel
import ca.gosyer.jui.ui.settings.SettingsAdvancedViewModel
import ca.gosyer.jui.ui.settings.SettingsBackupViewModel
@@ -43,10 +45,11 @@ actual class ViewModelFactoryImpl(
private val downloadsFactory: (Boolean) -> DownloadsScreenViewModel,
private val extensionsFactory: () -> ExtensionsScreenViewModel,
private val libraryFactory: () -> LibraryScreenViewModel,
private val librarySettingsFactory: () -> LibrarySettingsViewModel,
private val debugOverlayFactory: () -> DebugOverlayViewModel,
private val trayFactory: () -> TrayViewModel,
private val mainFactory: () -> MainViewModel,
private val mangaFactory: (params: ca.gosyer.jui.ui.manga.MangaScreenViewModel.Params) -> ca.gosyer.jui.ui.manga.MangaScreenViewModel,
private val mangaFactory: (params: MangaScreenViewModel.Params) -> MangaScreenViewModel,
private val readerFactory: (params: ReaderMenuViewModel.Params) -> ReaderMenuViewModel,
private val settingsAdvancedFactory: () -> SettingsAdvancedViewModel,
private val themesFactory: () -> ThemesViewModel,
@@ -73,10 +76,11 @@ actual class ViewModelFactoryImpl(
DownloadsScreenViewModel::class -> downloadsFactory(arg1 as Boolean)
ExtensionsScreenViewModel::class -> extensionsFactory()
LibraryScreenViewModel::class -> libraryFactory()
LibrarySettingsViewModel::class -> librarySettingsFactory()
DebugOverlayViewModel::class -> debugOverlayFactory()
TrayViewModel::class -> trayFactory()
MainViewModel::class -> mainFactory()
ca.gosyer.jui.ui.manga.MangaScreenViewModel::class -> mangaFactory(arg1 as ca.gosyer.jui.ui.manga.MangaScreenViewModel.Params)
MangaScreenViewModel::class -> mangaFactory(arg1 as MangaScreenViewModel.Params)
ReaderMenuViewModel::class -> readerFactory(arg1 as ReaderMenuViewModel.Params)
SettingsAdvancedViewModel::class -> settingsAdvancedFactory()
ThemesViewModel::class -> themesFactory()