diff --git a/data/src/jvmMain/kotlin/ca/gosyer/data/library/LibraryPreferences.kt b/data/src/jvmMain/kotlin/ca/gosyer/data/library/LibraryPreferences.kt index d8bdc08a..38bd3a51 100644 --- a/data/src/jvmMain/kotlin/ca/gosyer/data/library/LibraryPreferences.kt +++ b/data/src/jvmMain/kotlin/ca/gosyer/data/library/LibraryPreferences.kt @@ -16,6 +16,14 @@ class LibraryPreferences(private val preferenceStore: PreferenceStore) { return preferenceStore.getJsonObject("display_mode", DisplayMode.CompactGrid, DisplayMode.serializer()) } + fun gridColumns(): Preference { + return preferenceStore.getInt("grid_columns", 0) + } + + fun gridSize(): Preference { + return preferenceStore.getInt("grid_size", 160) + } + fun showAllCategory(): Preference { return preferenceStore.getBoolean("show_all_category", false) } diff --git a/data/src/jvmMain/kotlin/ca/gosyer/data/library/model/DisplayMode.kt b/data/src/jvmMain/kotlin/ca/gosyer/data/library/model/DisplayMode.kt index d3c73cd5..113848dd 100644 --- a/data/src/jvmMain/kotlin/ca/gosyer/data/library/model/DisplayMode.kt +++ b/data/src/jvmMain/kotlin/ca/gosyer/data/library/model/DisplayMode.kt @@ -6,13 +6,17 @@ package ca.gosyer.data.library.model +import ca.gosyer.i18n.MR +import dev.icerock.moko.resources.StringResource import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient @Serializable -enum class DisplayMode { - CompactGrid, - ComfortableGrid, - List; +enum class DisplayMode(@Transient val res: StringResource) { + CompactGrid(MR.strings.display_compact), + ComfortableGrid(MR.strings.display_comfortable), + CoverOnlyGrid(MR.strings.display_cover_only), + List(MR.strings.display_list); companion object { val values = values() diff --git a/i18n/src/commonMain/resources/MR/values/base/strings.xml b/i18n/src/commonMain/resources/MR/values/base/strings.xml index 23d02eee..a1871258 100644 --- a/i18n/src/commonMain/resources/MR/values/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/values/base/strings.xml @@ -158,6 +158,17 @@ Show all category + Display mode + Compact grid + Comfortable grid + Cover-only grid + List + Items per row + Columns: %1$d + Columns: Adaptive, Size: %1$d + Adaptive + Columns + Size Reader Mode diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/LibraryScreen.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/LibraryScreen.kt index 80d90026..ba39b814 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/LibraryScreen.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/LibraryScreen.kt @@ -29,6 +29,8 @@ class LibraryScreen : Screen { categories = vm.categories.collectAsState().value, selectedCategoryIndex = vm.selectedCategoryIndex.collectAsState().value, displayMode = vm.displayMode.collectAsState().value, + gridColumns = vm.gridColumns.collectAsState().value, + gridSize = vm.gridSize.collectAsState().value, isLoading = vm.isLoading.collectAsState().value, error = vm.error.collectAsState().value, query = vm.query.collectAsState().value, diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/LibraryScreenViewModel.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/LibraryScreenViewModel.kt index cebb81b6..b75484ad 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/LibraryScreenViewModel.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/LibraryScreenViewModel.kt @@ -84,6 +84,8 @@ class LibraryScreenViewModel @Inject constructor( val selectedCategoryIndex = _selectedCategoryIndex.asStateFlow() val displayMode = libraryPreferences.displayMode().stateIn(scope) + val gridColumns = libraryPreferences.gridColumns().stateIn(scope) + val gridSize = libraryPreferences.gridSize().stateIn(scope) private val _isLoading = MutableStateFlow(true) val isLoading = _isLoading.asStateFlow() diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryMangaList.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryMangaList.kt new file mode 100644 index 00000000..7aba07d9 --- /dev/null +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryMangaList.kt @@ -0,0 +1,94 @@ +/* + * 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.ui.library.components + +import androidx.compose.foundation.layout.Box +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.FilterQuality +import androidx.compose.ui.unit.dp +import ca.gosyer.data.models.Manga +import ca.gosyer.ui.base.components.VerticalScrollbar +import ca.gosyer.ui.base.components.rememberScrollbarAdapter +import ca.gosyer.uicore.components.MangaListItem +import ca.gosyer.uicore.components.MangaListItemImage +import ca.gosyer.uicore.components.MangaListItemTitle +import io.kamel.image.lazyPainterResource + +@Composable +fun LibraryMangaList( + library: List, + onClickManga: (Long) -> Unit, + onRemoveMangaClicked: (Long) -> Unit +) { + Box { + val state = rememberLazyListState() + LazyColumn( + state = state, + modifier = Modifier.fillMaxSize() + ) { + items(library) { manga -> + LibraryMangaListItem( + modifier = Modifier.libraryMangaModifier( + { onClickManga(manga.id) }, + { onRemoveMangaClicked(manga.id) } + ), + manga = manga, + unread = manga.unreadCount, + downloaded = manga.downloadCount, + ) + } + } + VerticalScrollbar( + rememberScrollbarAdapter(state), + Modifier.align(Alignment.CenterEnd) + .fillMaxHeight() + .padding(horizontal = 4.dp, vertical = 8.dp) + ) + } +} + +@Composable +private fun LibraryMangaListItem( + modifier: Modifier, + manga: Manga, + unread: Int?, + downloaded: Int?, +) { + val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium) + MangaListItem( + modifier = modifier then Modifier + .requiredHeight(56.dp) + .padding(horizontal = 16.dp), + ) { + MangaListItemImage( + modifier = Modifier + .size(40.dp) + .clip(MaterialTheme.shapes.medium), + cover = cover, + contentDescription = manga.title + ) + MangaListItemTitle( + modifier = Modifier + .weight(1f) + .padding(horizontal = 16.dp), + text = manga.title, + ) + LibraryMangaBadges(unread, downloaded) + } +} diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryPager.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryPager.kt index 2a420af8..ef1e67a5 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryPager.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryPager.kt @@ -21,6 +21,8 @@ fun LibraryPager( pagerState: PagerState, categories: List, displayMode: DisplayMode, + gridColumns: Int, + gridSize: Int, getLibraryForPage: @Composable (Long) -> State>, onClickManga: (Long) -> Unit, onRemoveMangaClicked: (Long) -> Unit @@ -32,17 +34,30 @@ fun LibraryPager( when (displayMode) { DisplayMode.CompactGrid -> LibraryMangaCompactGrid( library = library, + gridColumns = gridColumns, + gridSize = gridSize, onClickManga = onClickManga, onRemoveMangaClicked = onRemoveMangaClicked ) - /*DisplayMode.ComfortableGrid -> LibraryMangaComfortableGrid( + DisplayMode.ComfortableGrid -> LibraryMangaComfortableGrid( library = library, - onClickManga = onClickManga + gridColumns = gridColumns, + gridSize = gridSize, + onClickManga = onClickManga, + onRemoveMangaClicked = onRemoveMangaClicked + ) + DisplayMode.CoverOnlyGrid -> LibraryMangaCoverOnlyGrid( + library = library, + gridColumns = gridColumns, + gridSize = gridSize, + onClickManga = onClickManga, + onRemoveMangaClicked = onRemoveMangaClicked ) DisplayMode.List -> LibraryMangaList( library = library, - onClickManga = onClickManga - )*/ + onClickManga = onClickManga, + onRemoveMangaClicked = onRemoveMangaClicked + ) else -> Box {} } } diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryScreenContent.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryScreenContent.kt index eca2a71c..ec05a58a 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryScreenContent.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/LibraryScreenContent.kt @@ -33,6 +33,8 @@ fun LibraryScreenContent( categories: List, selectedCategoryIndex: Int, displayMode: DisplayMode, + gridColumns: Int, + gridSize: Int, isLoading: Boolean, error: String?, query: String, @@ -60,6 +62,8 @@ fun LibraryScreenContent( categories = categories, selectedCategoryIndex = selectedCategoryIndex, displayMode = displayMode, + gridColumns = gridColumns, + gridSize = gridSize, isLoading = isLoading, error = error, query = query, @@ -75,6 +79,8 @@ fun LibraryScreenContent( categories = categories, selectedCategoryIndex = selectedCategoryIndex, displayMode = displayMode, + gridColumns = gridColumns, + gridSize = gridSize, isLoading = isLoading, error = error, query = query, @@ -94,6 +100,8 @@ fun WideLibraryScreenContent( categories: List, selectedCategoryIndex: Int, displayMode: DisplayMode, + gridColumns: Int, + gridSize: Int, isLoading: Boolean, error: String?, query: String, @@ -129,6 +137,8 @@ fun WideLibraryScreenContent( pagerState = pagerState, categories = categories, displayMode = displayMode, + gridColumns = gridColumns, + gridSize = gridSize, getLibraryForPage = getLibraryForPage, onClickManga = onClickManga, onRemoveMangaClicked = onRemoveMangaClicked @@ -144,6 +154,8 @@ fun ThinLibraryScreenContent( categories: List, selectedCategoryIndex: Int, displayMode: DisplayMode, + gridColumns: Int, + gridSize: Int, isLoading: Boolean, error: String?, query: String, @@ -185,6 +197,8 @@ fun ThinLibraryScreenContent( pagerState = pagerState, categories = categories, displayMode = displayMode, + gridColumns = gridColumns, + gridSize = gridSize, getLibraryForPage = getLibraryForPage, onClickManga = onClickManga, onRemoveMangaClicked = onRemoveMangaClicked diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/MangaComfortableGrid.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/MangaComfortableGrid.kt new file mode 100644 index 00000000..dedb7c67 --- /dev/null +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/MangaComfortableGrid.kt @@ -0,0 +1,121 @@ +/* + * 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.ui.library.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +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.lazy.GridCells +import androidx.compose.foundation.lazy.LazyVerticalGrid +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.FilterQuality +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import ca.gosyer.data.models.Manga +import ca.gosyer.ui.base.components.VerticalScrollbar +import ca.gosyer.ui.base.components.rememberScrollbarAdapter +import ca.gosyer.uicore.image.KamelImage +import io.kamel.image.lazyPainterResource + +@Composable +fun LibraryMangaComfortableGrid( + library: List, + gridColumns: Int, + gridSize: Int, + onClickManga: (Long) -> Unit, + onRemoveMangaClicked: (Long) -> Unit +) { + Box { + val state = rememberLazyListState() + val cells = if (gridColumns < 1) { + GridCells.Adaptive(gridSize.dp) + } else { + GridCells.Fixed(gridColumns) + } + LazyVerticalGrid( + cells = cells, + state = state, + modifier = Modifier.fillMaxSize().padding(4.dp) + ) { + items(library) { manga -> + LibraryMangaComfortableGridItem( + modifier = Modifier.libraryMangaModifier( + { onClickManga(manga.id) }, + { onRemoveMangaClicked(manga.id) } + ), + manga = manga, + unread = manga.unreadCount, + downloaded = manga.downloadCount + ) + } + } + VerticalScrollbar( + rememberScrollbarAdapter(state), + Modifier.align(Alignment.CenterEnd) + .fillMaxHeight() + .padding(horizontal = 4.dp, vertical = 8.dp) + ) + } +} + +@Composable +private fun LibraryMangaComfortableGridItem( + modifier: Modifier, + manga: Manga, + unread: Int?, + downloaded: Int? +) { + val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium) + val fontStyle = LocalTextStyle.current.merge( + TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp) + ) + + Box( + modifier = Modifier + .padding(4.dp) + .fillMaxWidth() + .clip(MaterialTheme.shapes.medium) then modifier + ) { + Column { + KamelImage( + cover, + contentDescription = manga.title, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(3f / 4f) + .clip(MaterialTheme.shapes.medium), + contentScale = ContentScale.Crop + ) + Text( + text = manga.title, + style = fontStyle, + maxLines = 3, + modifier = Modifier.padding(vertical = 4.dp, horizontal = 4.dp) + ) + } + LibraryMangaBadges( + unread = unread, + downloaded = downloaded, + modifier = Modifier.padding(4.dp) + ) + } +} diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/MangaCompactGrid.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/MangaCompactGrid.kt index 5c489bd7..b2d69a22 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/MangaCompactGrid.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/MangaCompactGrid.kt @@ -47,13 +47,20 @@ expect fun Modifier.libraryMangaModifier( @Composable fun LibraryMangaCompactGrid( library: List, - onClickManga: (Long) -> Unit = {}, - onRemoveMangaClicked: (Long) -> Unit = {} + gridColumns: Int, + gridSize: Int, + onClickManga: (Long) -> Unit, + onRemoveMangaClicked: (Long) -> Unit ) { Box { val state = rememberLazyListState() + val cells = if (gridColumns < 1) { + GridCells.Adaptive(gridSize.dp) + } else { + GridCells.Fixed(gridColumns) + } LazyVerticalGrid( - cells = GridCells.Adaptive(160.dp), + cells = cells, state = state, modifier = Modifier.fillMaxSize().padding(4.dp) ) { @@ -96,7 +103,12 @@ private fun LibraryMangaCompactGridItem( .aspectRatio(3f / 4f) .clip(MaterialTheme.shapes.medium) then modifier ) { - KamelImage(cover, manga.title, contentScale = ContentScale.Crop) + KamelImage( + cover, + manga.title, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) Box(modifier = Modifier.fillMaxSize().then(shadowGradient)) Text( text = manga.title, diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/MangaCoverOnlyGrid.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/MangaCoverOnlyGrid.kt new file mode 100644 index 00000000..0770f9ee --- /dev/null +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/library/components/MangaCoverOnlyGrid.kt @@ -0,0 +1,101 @@ +/* + * 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.ui.library.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +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.lazy.GridCells +import androidx.compose.foundation.lazy.LazyVerticalGrid +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.FilterQuality +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import ca.gosyer.data.models.Manga +import ca.gosyer.ui.base.components.VerticalScrollbar +import ca.gosyer.ui.base.components.rememberScrollbarAdapter +import ca.gosyer.uicore.image.KamelImage +import io.kamel.image.lazyPainterResource + +@Composable +fun LibraryMangaCoverOnlyGrid( + library: List, + gridColumns: Int, + gridSize: Int, + onClickManga: (Long) -> Unit, + onRemoveMangaClicked: (Long) -> Unit +) { + Box { + val state = rememberLazyListState() + val cells = if (gridColumns < 1) { + GridCells.Adaptive(gridSize.dp) + } else { + GridCells.Fixed(gridColumns) + } + LazyVerticalGrid( + cells = cells, + state = state, + modifier = Modifier.fillMaxSize().padding(4.dp) + ) { + items(library) { manga -> + LibraryMangaCoverOnlyGridItem( + modifier = Modifier.libraryMangaModifier( + { onClickManga(manga.id) }, + { onRemoveMangaClicked(manga.id) } + ), + manga = manga, + unread = manga.unreadCount, + downloaded = manga.downloadCount + ) + } + } + VerticalScrollbar( + rememberScrollbarAdapter(state), + Modifier.align(Alignment.CenterEnd) + .fillMaxHeight() + .padding(horizontal = 4.dp, vertical = 8.dp) + ) + } +} + +@Composable +private fun LibraryMangaCoverOnlyGridItem( + modifier: Modifier, + manga: Manga, + unread: Int?, + downloaded: Int? +) { + val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium) + + Box( + modifier = Modifier.padding(4.dp) + .fillMaxWidth() + .aspectRatio(3f / 4f) + .clip(MaterialTheme.shapes.medium) then modifier + ) { + KamelImage( + cover, + contentDescription = manga.title, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + LibraryMangaBadges( + unread = unread, + downloaded = downloaded, + modifier = Modifier.padding(4.dp) + ) + } +} diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/settings/SettingsLibraryScreen.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/settings/SettingsLibraryScreen.kt index 9760d421..06814d2c 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/settings/SettingsLibraryScreen.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/settings/SettingsLibraryScreen.kt @@ -6,27 +6,44 @@ package ca.gosyer.ui.settings +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ContentAlpha +import androidx.compose.material.Divider +import androidx.compose.material.LocalContentColor import androidx.compose.material.Scaffold +import androidx.compose.material.Slider +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import ca.gosyer.core.logging.CKLogger import ca.gosyer.data.library.LibraryPreferences +import ca.gosyer.data.library.model.DisplayMode import ca.gosyer.data.server.interactions.CategoryInteractionHandler import ca.gosyer.i18n.MR import ca.gosyer.ui.base.components.VerticalScrollbar import ca.gosyer.ui.base.components.rememberScrollbarAdapter +import ca.gosyer.ui.base.dialog.getMaterialDialogProperties import ca.gosyer.ui.base.navigation.Toolbar +import ca.gosyer.ui.base.prefs.ChoicePreference import ca.gosyer.ui.base.prefs.PreferenceRow -import ca.gosyer.ui.base.prefs.SwitchPreference import ca.gosyer.ui.categories.rememberCategoriesLauncher import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow import ca.gosyer.uicore.resources.stringResource @@ -36,12 +53,17 @@ import ca.gosyer.uicore.vm.viewModel import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.core.screen.uniqueScreenKey +import com.vanpra.composematerialdialogs.MaterialDialog +import com.vanpra.composematerialdialogs.MaterialDialogState +import com.vanpra.composematerialdialogs.rememberMaterialDialogState +import com.vanpra.composematerialdialogs.title import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import me.tatarka.inject.annotations.Inject +import kotlin.math.roundToInt class SettingsLibraryScreen : Screen { override val key: ScreenKey = uniqueScreenKey @@ -52,6 +74,10 @@ class SettingsLibraryScreen : Screen { val categoriesLauncher = rememberCategoriesLauncher(vm::refreshCategoryCount) SettingsLibraryScreenContent( showAllCategory = vm.showAllCategory, + displayMode = vm.displayMode, + displayModeChoices = vm.getDisplayModeChoices(), + gridColumns = vm.gridColumns, + gridSize = vm.gridSize, categoriesSize = vm.categories.collectAsState().value, openCategoriesScreen = categoriesLauncher::open ) @@ -65,6 +91,10 @@ class SettingsLibraryViewModel @Inject constructor( contextWrapper: ContextWrapper ) : ViewModel(contextWrapper) { + val displayMode = libraryPreferences.displayMode().asStateFlow() + val gridColumns = libraryPreferences.gridColumns().asStateFlow() + val gridSize = libraryPreferences.gridSize().asStateFlow() + val showAllCategory = libraryPreferences.showAllCategory().asStateFlow() private val _categories = MutableStateFlow(0) val categories = _categories.asStateFlow() @@ -84,11 +114,19 @@ class SettingsLibraryViewModel @Inject constructor( .launchIn(scope) } + @Composable + fun getDisplayModeChoices() = DisplayMode.values() + .associateWith { stringResource(it.res) } + private companion object : CKLogger({}) } @Composable fun SettingsLibraryScreenContent( + displayMode: PreferenceMutableStateFlow, + displayModeChoices: Map, + gridColumns: PreferenceMutableStateFlow, + gridSize: PreferenceMutableStateFlow, showAllCategory: PreferenceMutableStateFlow, categoriesSize: Int, openCategoriesScreen: () -> Unit @@ -102,10 +140,29 @@ fun SettingsLibraryScreenContent( val state = rememberLazyListState() LazyColumn(Modifier.fillMaxSize(), state) { item { + ChoicePreference( + preference = displayMode, + choices = displayModeChoices, + title = stringResource(MR.strings.display_mode) + ) + } + item { + val displayModePref by displayMode.collectAsState() + GridPreference( + columnPreference = gridColumns, + sizePreference = gridSize, + title = stringResource(MR.strings.items_per_row), + enabled = displayModePref != DisplayMode.List + ) + } + /*item { SwitchPreference( preference = showAllCategory, title = stringResource(MR.strings.show_all_category) ) + }*/ + item { + Divider() } item { PreferenceRow( @@ -124,3 +181,114 @@ fun SettingsLibraryScreenContent( } } } + +@Composable +private fun GridPreference( + columnPreference: PreferenceMutableStateFlow, + sizePreference: PreferenceMutableStateFlow, + title: String, + enabled: Boolean +) { + val columnPrefValue by columnPreference.collectAsState() + val sizePrefValue by sizePreference.collectAsState() + val dialogState = rememberMaterialDialogState() + PreferenceRow( + title = title, + subtitle = if (columnPrefValue < 1) { + stringResource(MR.strings.items_per_row_sub_adaptive, sizePrefValue) + } else { + stringResource(MR.strings.items_per_row_sub, columnPrefValue) + }, + onClick = { + dialogState.show() + }, + enabled = enabled + ) + GridPrefDialog( + state = dialogState, + initialColumns = columnPrefValue, + initialSize = sizePrefValue, + title = title, + onSelected = { columns, size -> + columnPreference.value = columns + sizePreference.value = size + } + ) +} + +@Composable +private fun GridPrefDialog( + state: MaterialDialogState, + initialColumns: Int, + initialSize: Int, + onCloseRequest: () -> Unit = {}, + onSelected: (columns: Int, size: Int) -> Unit, + title: String +) { + var columns by remember(initialColumns) { mutableStateOf(initialColumns.toFloat()) } + var size by remember(initialSize) { mutableStateOf(initialSize.toFloat()) } + MaterialDialog( + state, + buttons = { + positiveButton(stringResource(MR.strings.action_ok)) { + val sizeInt = size.roundToInt() + val newSize = (10 - sizeInt % 10) + sizeInt + onSelected(columns.roundToInt(), newSize) + } + negativeButton(stringResource(MR.strings.action_cancel)) + }, + properties = getMaterialDialogProperties(), + onCloseRequest = { + state.hide() + onCloseRequest() + } + ) { + title(title) + Column(Modifier.padding(horizontal = 8.dp)) { + Text(stringResource(MR.strings.grid_columns) + ":") + Slider( + value = columns, + onValueChange = { + columns = it + }, + modifier = Modifier.fillMaxWidth(), + valueRange = 0F..10F, + steps = 10 - 2 + ) + val columnsInt = columns.roundToInt() + val adaptive = columnsInt < 1 + Text( + if (adaptive) { + stringResource(MR.strings.adaptive) + } else { + columnsInt.toString() + }, + color = LocalContentColor.current.copy(alpha = ContentAlpha.disabled), + fontSize = 12.sp + ) + + AnimatedVisibility(adaptive) { + Column { + Spacer(Modifier.height(16.dp)) + Text(stringResource(MR.strings.grid_size) + ":") + Slider( + value = size, + onValueChange = { + size = it + }, + modifier = Modifier.fillMaxWidth(), + valueRange = 90F..300F, + steps = 21 - 2 + ) + val sizeInt = size.roundToInt() + val newSize = (10 - sizeInt % 10) + sizeInt + Text( + newSize.toString(), + color = LocalContentColor.current.copy(alpha = ContentAlpha.disabled), + fontSize = 12.sp + ) + } + } + } + } +}