From 8666c0f3ff1663a3bea18886e1317f4d52e1ce22 Mon Sep 17 00:00:00 2001 From: Syer10 Date: Sun, 6 Mar 2022 17:38:25 -0500 Subject: [PATCH] Improve Source display, in library badge, add comfortable and list display --- .../gosyer/data/catalog/CatalogPreferences.kt | 5 + .../resources/MR/values/base/strings.xml | 1 + .../gosyer/ui/sources/browse/SourceScreen.kt | 4 + .../sources/browse/SourceScreenViewModel.kt | 17 ++ .../browse/components/SourceMangaBadges.kt | 38 +++++ .../components/SourceMangaComfortableGrid.kt | 123 ++++++++++++++ .../components/SourceMangaCompactGrid.kt | 135 +++++++++++++++ .../browse/components/SourceMangaList.kt | 97 +++++++++++ .../browse/components/SourceScreenContent.kt | 156 +++++++++++++----- 9 files changed, 534 insertions(+), 42 deletions(-) create mode 100644 presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaBadges.kt create mode 100644 presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaComfortableGrid.kt create mode 100644 presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaCompactGrid.kt create mode 100644 presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaList.kt diff --git a/data/src/jvmMain/kotlin/ca/gosyer/data/catalog/CatalogPreferences.kt b/data/src/jvmMain/kotlin/ca/gosyer/data/catalog/CatalogPreferences.kt index a0402f42..603769d4 100644 --- a/data/src/jvmMain/kotlin/ca/gosyer/data/catalog/CatalogPreferences.kt +++ b/data/src/jvmMain/kotlin/ca/gosyer/data/catalog/CatalogPreferences.kt @@ -8,10 +8,15 @@ package ca.gosyer.data.catalog import ca.gosyer.core.prefs.Preference import ca.gosyer.core.prefs.PreferenceStore +import ca.gosyer.data.library.model.DisplayMode import java.util.Locale class CatalogPreferences(private val preferenceStore: PreferenceStore) { fun languages(): Preference> { return preferenceStore.getStringSet("enabled_langs", setOf("en", Locale.getDefault().language)) } + + fun displayMode(): Preference { + return preferenceStore.getJsonObject("display_mode", DisplayMode.CompactGrid, DisplayMode.serializer()) + } } diff --git a/i18n/src/commonMain/resources/MR/values/base/strings.xml b/i18n/src/commonMain/resources/MR/values/base/strings.xml index a1871258..db45b219 100644 --- a/i18n/src/commonMain/resources/MR/values/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/values/base/strings.xml @@ -89,6 +89,7 @@ Latest Reset Filter + In library Default diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/SourceScreen.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/SourceScreen.kt index bfee4c0c..f99ff0aa 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/SourceScreen.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/SourceScreen.kt @@ -39,6 +39,9 @@ class SourceScreen(val source: Source) : Screen { onMangaClick = { navigator push MangaScreen(it) }, onCloseSourceTabClick = sourcesNavigator::remove, onSourceSettingsClick = { navigator push SourceSettingsScreen(it) }, + displayMode = sourceVM.displayMode.collectAsState().value, + gridColumns = sourceVM.gridColumns.collectAsState().value, + gridSize = sourceVM.gridSize.collectAsState().value, mangas = sourceVM.mangas.collectAsState().value, hasNextPage = sourceVM.hasNextPage.collectAsState().value, loading = sourceVM.loading.collectAsState().value, @@ -51,6 +54,7 @@ class SourceScreen(val source: Source) : Screen { setMode = sourceVM::setMode, loadNextPage = sourceVM::loadNextPage, setUsingFilters = sourceVM::setUsingFilters, + onSelectDisplayMode = sourceVM::selectDisplayMode, // FilterVM filters = filterVM.filters.collectAsState().value, showingFilters = filterVM.showingFilters.collectAsState().value, diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/SourceScreenViewModel.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/SourceScreenViewModel.kt index f457c5b3..3af7d448 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/SourceScreenViewModel.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/SourceScreenViewModel.kt @@ -7,6 +7,9 @@ package ca.gosyer.ui.sources.browse import ca.gosyer.core.logging.CKLogger +import ca.gosyer.data.catalog.CatalogPreferences +import ca.gosyer.data.library.LibraryPreferences +import ca.gosyer.data.library.model.DisplayMode import ca.gosyer.data.models.Manga import ca.gosyer.data.models.MangaPage import ca.gosyer.data.models.Source @@ -24,19 +27,29 @@ import me.tatarka.inject.annotations.Inject class SourceScreenViewModel( private val source: Source, private val sourceHandler: SourceInteractionHandler, + private val catalogPreferences: CatalogPreferences, + private val libraryPreferences: LibraryPreferences, contextWrapper: ContextWrapper ) : ViewModel(contextWrapper) { @Inject constructor( sourceHandler: SourceInteractionHandler, + catalogPreferences: CatalogPreferences, + libraryPreferences: LibraryPreferences, contextWrapper: ContextWrapper, params: Params ) : this( params.source, sourceHandler, + catalogPreferences, + libraryPreferences, contextWrapper ) + val displayMode = catalogPreferences.displayMode().stateIn(scope) + val gridColumns = libraryPreferences.gridColumns().stateIn(scope) + val gridSize = libraryPreferences.gridSize().stateIn(scope) + private val _mangas = MutableStateFlow(emptyList()) val mangas = _mangas.asStateFlow() @@ -139,6 +152,10 @@ class SourceScreenViewModel( startSearch(sourceSearchQuery.value) } + fun selectDisplayMode(displayMode: DisplayMode) { + catalogPreferences.displayMode().set(displayMode) + } + data class Params(val source: Source) private companion object : CKLogger({}) diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaBadges.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaBadges.kt new file mode 100644 index 00000000..3357c3cc --- /dev/null +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaBadges.kt @@ -0,0 +1,38 @@ +/* + * 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.sources.browse.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +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.i18n.MR +import ca.gosyer.uicore.resources.stringResource + +@Composable +fun SourceMangaBadges( + inLibrary: Boolean, + modifier: Modifier = Modifier, +) { + if (!inLibrary) return + + Row(modifier = modifier.clip(MaterialTheme.shapes.medium)) { + Text( + text = stringResource(MR.strings.in_library), + modifier = Modifier.background(MaterialTheme.colors.primary).then(BadgesInnerPadding), + style = MaterialTheme.typography.caption, + color = MaterialTheme.colors.onPrimary + ) + } +} + +private val BadgesInnerPadding = Modifier.padding(horizontal = 3.dp, vertical = 1.dp) diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaComfortableGrid.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaComfortableGrid.kt new file mode 100644 index 00000000..3c6fa114 --- /dev/null +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaComfortableGrid.kt @@ -0,0 +1,123 @@ +/* + * 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.sources.browse.components + +import androidx.compose.foundation.clickable +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.itemsIndexed +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.runtime.LaunchedEffect +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 SourceMangaComfortableGrid( + mangas: List, + gridColumns: Int, + gridSize: Int, + onClickManga: (Long) -> Unit, + hasNextPage: Boolean = false, + onLoadNextPage: () -> 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) + ) { + itemsIndexed(mangas) { index, manga -> + if (hasNextPage && index == mangas.lastIndex) { + LaunchedEffect(Unit) { onLoadNextPage() } + } + SourceMangaComfortableGridItem( + modifier = Modifier.clickable( + onClick = { onClickManga(manga.id) } + ), + manga = manga, + inLibrary = manga.inLibrary + ) + } + } + VerticalScrollbar( + rememberScrollbarAdapter(state), + Modifier.align(Alignment.CenterEnd) + .fillMaxHeight() + .padding(horizontal = 4.dp, vertical = 8.dp) + ) + } +} + +@Composable +private fun SourceMangaComfortableGridItem( + modifier: Modifier, + manga: Manga, + inLibrary: Boolean +) { + 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) + ) + } + SourceMangaBadges( + inLibrary = inLibrary, + modifier = Modifier.padding(4.dp) + ) + } +} diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaCompactGrid.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaCompactGrid.kt new file mode 100644 index 00000000..8e456736 --- /dev/null +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaCompactGrid.kt @@ -0,0 +1,135 @@ +/* + * 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.sources.browse.components + +import androidx.compose.foundation.clickable +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.itemsIndexed +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.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +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 SourceMangaCompactGrid( + mangas: List, + gridColumns: Int, + gridSize: Int, + onClickManga: (Long) -> Unit, + hasNextPage: Boolean = false, + onLoadNextPage: () -> 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) + ) { + itemsIndexed(mangas) { index, manga -> + if (hasNextPage && index == mangas.lastIndex) { + LaunchedEffect(Unit) { onLoadNextPage() } + } + SourceMangaCompactGridItem( + modifier = Modifier.clickable( + onClick = { onClickManga(manga.id) } + ), + manga = manga, + inLibrary = manga.inLibrary + ) + } + } + VerticalScrollbar( + rememberScrollbarAdapter(state), + Modifier.align(Alignment.CenterEnd) + .fillMaxHeight() + .padding(horizontal = 4.dp, vertical = 8.dp) + ) + } +} + +@Composable +private fun SourceMangaCompactGridItem( + modifier: Modifier, + manga: Manga, + inLibrary: Boolean +) { + 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() + .aspectRatio(3f / 4f) + .clip(MaterialTheme.shapes.medium) then modifier + ) { + KamelImage( + cover, + manga.title, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + Box(modifier = Modifier.fillMaxSize().then(shadowGradient)) + Text( + text = manga.title, + color = Color.White, + style = fontStyle, + maxLines = 2, + modifier = Modifier.align(Alignment.BottomStart).padding(8.dp) + ) + SourceMangaBadges( + inLibrary = inLibrary, + modifier = Modifier.padding(4.dp) + ) + } +} + +private val shadowGradient = Modifier.drawWithCache { + val gradient = Brush.linearGradient( + 0.75f to Color.Transparent, + 1.0f to Color(0xAA000000), + start = Offset(0f, 0f), + end = Offset(0f, size.height) + ) + onDrawBehind { + drawRect(gradient) + } +} diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaList.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaList.kt new file mode 100644 index 00000000..4206dd6c --- /dev/null +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceMangaList.kt @@ -0,0 +1,97 @@ +/* + * 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.sources.browse.components + +import androidx.compose.foundation.clickable +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.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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 SourceMangaList( + mangas: List, + onClickManga: (Long) -> Unit, + hasNextPage: Boolean = false, + onLoadNextPage: () -> Unit, +) { + Box { + val state = rememberLazyListState() + LazyColumn( + state = state, + modifier = Modifier.fillMaxSize() + ) { + itemsIndexed(mangas) { index, manga -> + if (hasNextPage && index == mangas.lastIndex) { + LaunchedEffect(Unit) { onLoadNextPage() } + } + MangaListItem( + modifier = Modifier.clickable( + onClick = { onClickManga(manga.id) } + ), + manga = manga, + inLibrary = manga.inLibrary + ) + } + } + VerticalScrollbar( + rememberScrollbarAdapter(state), + Modifier.align(Alignment.CenterEnd) + .fillMaxHeight() + .padding(horizontal = 4.dp, vertical = 8.dp) + ) + } +} + +@Composable +private fun MangaListItem( + modifier: Modifier, + manga: Manga, + inLibrary: Boolean +) { + 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, + ) + SourceMangaBadges(inLibrary) + } +} diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceScreenContent.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceScreenContent.kt index 42d9cc74..fb7bcba2 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceScreenContent.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/sources/browse/components/SourceScreenContent.kt @@ -15,17 +15,14 @@ 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.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.GridCells -import androidx.compose.foundation.lazy.LazyVerticalGrid -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.BottomSheetScaffold import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem import androidx.compose.material.ExtendedFloatingActionButton import androidx.compose.material.Icon import androidx.compose.material.Scaffold @@ -35,29 +32,31 @@ import androidx.compose.material.icons.rounded.Explore import androidx.compose.material.icons.rounded.FilterList import androidx.compose.material.icons.rounded.NewReleases import androidx.compose.material.icons.rounded.Settings +import androidx.compose.material.icons.rounded.ViewModule import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach +import ca.gosyer.data.library.model.DisplayMode import ca.gosyer.data.models.Manga import ca.gosyer.data.models.Source import ca.gosyer.i18n.MR -import ca.gosyer.ui.base.components.VerticalScrollbar -import ca.gosyer.ui.base.components.rememberScrollbarAdapter import ca.gosyer.ui.base.navigation.ActionItem import ca.gosyer.ui.base.navigation.BackHandler import ca.gosyer.ui.base.navigation.Toolbar import ca.gosyer.ui.sources.browse.filter.SourceFiltersMenu import ca.gosyer.ui.sources.browse.filter.model.SourceFiltersView import ca.gosyer.uicore.components.LoadingScreen -import ca.gosyer.uicore.components.MangaGridItem import ca.gosyer.uicore.resources.stringResource -import io.kamel.image.lazyPainterResource @Composable fun SourceScreenContent( @@ -65,6 +64,9 @@ fun SourceScreenContent( onMangaClick: (Long) -> Unit, onCloseSourceTabClick: (Source) -> Unit, onSourceSettingsClick: (Long) -> Unit, + displayMode: DisplayMode, + gridColumns: Int, + gridSize: Int, mangas: List, hasNextPage: Boolean, loading: Boolean, @@ -77,6 +79,7 @@ fun SourceScreenContent( setMode: (Boolean) -> Unit, loadNextPage: () -> Unit, setUsingFilters: (Boolean) -> Unit, + onSelectDisplayMode: (DisplayMode) -> Unit, // filter filters: List>, showingFilters: Boolean, @@ -103,6 +106,9 @@ fun SourceScreenContent( onMangaClick = onMangaClick, onCloseSourceTabClick = onCloseSourceTabClick, onSourceSettingsClick = onSourceSettingsClick, + displayMode = displayMode, + gridColumns = gridColumns, + gridSize = gridSize, mangas = mangas, hasNextPage = hasNextPage, loading = loading, @@ -118,6 +124,7 @@ fun SourceScreenContent( showingFilters = showingFilters, showFilterButton = showFilterButton, setShowingFilters = setShowingFilters, + onSelectDisplayMode = onSelectDisplayMode, resetFiltersClicked = resetFiltersClicked ) } else { @@ -126,6 +133,9 @@ fun SourceScreenContent( onMangaClick = onMangaClick, onCloseSourceTabClick = onCloseSourceTabClick, onSourceSettingsClick = onSourceSettingsClick, + displayMode = displayMode, + gridColumns = gridColumns, + gridSize = gridSize, mangas = mangas, hasNextPage = hasNextPage, loading = loading, @@ -141,6 +151,7 @@ fun SourceScreenContent( showingFilters = showingFilters, showFilterButton = showFilterButton, setShowingFilters = setShowingFilters, + onSelectDisplayMode = onSelectDisplayMode, resetFiltersClicked = resetFiltersClicked ) } @@ -153,6 +164,9 @@ private fun SourceWideScreenContent( onMangaClick: (Long) -> Unit, onCloseSourceTabClick: (Source) -> Unit, onSourceSettingsClick: (Long) -> Unit, + displayMode: DisplayMode, + gridColumns: Int, + gridSize: Int, mangas: List, hasNextPage: Boolean, loading: Boolean, @@ -169,6 +183,7 @@ private fun SourceWideScreenContent( showingFilters: Boolean, showFilterButton: Boolean, setShowingFilters: (Boolean) -> Unit, + onSelectDisplayMode: (DisplayMode) -> Unit, resetFiltersClicked: () -> Unit ) { Scaffold( @@ -186,11 +201,15 @@ private fun SourceWideScreenContent( showingFilters = showingFilters, onClickMode = setMode, onToggleFiltersClick = setShowingFilters, + onSelectDisplayMode = onSelectDisplayMode ) }, ) { padding -> Box(Modifier.padding(padding)) { MangaTable( + displayMode = displayMode, + gridColumns = gridColumns, + gridSize = gridSize, mangas = mangas, isLoading = loading, hasNextPage = hasNextPage, @@ -235,6 +254,9 @@ private fun SourceThinScreenContent( onMangaClick: (Long) -> Unit, onCloseSourceTabClick: (Source) -> Unit, onSourceSettingsClick: (Long) -> Unit, + displayMode: DisplayMode, + gridColumns: Int, + gridSize: Int, mangas: List, hasNextPage: Boolean, loading: Boolean, @@ -251,6 +273,7 @@ private fun SourceThinScreenContent( showingFilters: Boolean, showFilterButton: Boolean, setShowingFilters: (Boolean) -> Unit, + onSelectDisplayMode: (DisplayMode) -> Unit, resetFiltersClicked: () -> Unit ) { val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( @@ -288,6 +311,7 @@ private fun SourceThinScreenContent( showingFilters = showingFilters, onClickMode = setMode, onToggleFiltersClick = setShowingFilters, + onSelectDisplayMode = onSelectDisplayMode ) }, sheetContent = { @@ -306,6 +330,9 @@ private fun SourceThinScreenContent( ) { padding -> Box(Modifier.padding(padding)) { MangaTable( + displayMode = displayMode, + gridColumns = gridColumns, + gridSize = gridSize, mangas = mangas, isLoading = loading, hasNextPage = hasNextPage, @@ -358,7 +385,8 @@ fun SourceToolbar( isLatest: Boolean, showingFilters: Boolean, onClickMode: (Boolean) -> Unit, - onToggleFiltersClick: (Boolean) -> Unit + onToggleFiltersClick: (Boolean) -> Unit, + onSelectDisplayMode: (DisplayMode) -> Unit, ) { Toolbar( source.name, @@ -370,6 +398,12 @@ fun SourceToolbar( search = onSearch, searchSubmit = onSubmitSearch, actions = { + var displayModeSelectOpen by remember { mutableStateOf(false) } + DisplayModeSelect( + isVisible = displayModeSelectOpen, + onSelectDisplayMode = onSelectDisplayMode, + onDismissRequest = { displayModeSelectOpen = false } + ) getActionItems( isConfigurable = source.isConfigurable, onSourceSettingsClick = { @@ -383,14 +417,44 @@ fun SourceToolbar( }, onClickMode = { onClickMode(!isLatest) - } + }, + openDisplayModeSelect = { displayModeSelectOpen = true } ) + } ) } +@Composable +fun DisplayModeSelect( + isVisible: Boolean, + onSelectDisplayMode: (DisplayMode) -> Unit, + onDismissRequest: () -> Unit +) { + DropdownMenu( + isVisible, + onDismissRequest + ) { + val list = DisplayMode.values().toList() - DisplayMode.CoverOnlyGrid + list.fastForEach { + DropdownMenuItem( + onClick = { + onSelectDisplayMode(it) + onDismissRequest() + } + ) { + Text(stringResource(it.res)) + } + } + + } +} + @Composable private fun MangaTable( + displayMode: DisplayMode, + gridColumns: Int, + gridSize: Int, mangas: List, isLoading: Boolean = false, hasNextPage: Boolean = false, @@ -400,28 +464,30 @@ private fun MangaTable( if (isLoading || mangas.isEmpty()) { LoadingScreen(isLoading) } else { - val lazyListState = rememberLazyListState() - Box { - LazyVerticalGrid(GridCells.Adaptive(160.dp), state = lazyListState) { - itemsIndexed(mangas) { index, manga -> - if (hasNextPage && index == mangas.lastIndex) { - LaunchedEffect(Unit) { onLoadNextPage() } - } - MangaGridItem( - title = manga.title, - cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium), - onClick = { - onMangaClick(manga.id) - } - ) - } - } - VerticalScrollbar( - rememberScrollbarAdapter(lazyListState), - Modifier.align(Alignment.CenterEnd) - .fillMaxHeight() - .padding(horizontal = 4.dp, vertical = 8.dp) + when (displayMode) { + DisplayMode.CompactGrid -> SourceMangaCompactGrid( + mangas = mangas, + gridColumns = gridColumns, + gridSize = gridSize, + onClickManga = onMangaClick, + hasNextPage = hasNextPage, + onLoadNextPage = onLoadNextPage ) + DisplayMode.ComfortableGrid -> SourceMangaComfortableGrid( + mangas = mangas, + gridColumns = gridColumns, + gridSize = gridSize, + onClickManga = onMangaClick, + hasNextPage = hasNextPage, + onLoadNextPage = onLoadNextPage + ) + DisplayMode.List -> SourceMangaList( + mangas = mangas, + onClickManga = onMangaClick, + hasNextPage = hasNextPage, + onLoadNextPage = onLoadNextPage + ) + else -> Box {} } } } @@ -435,16 +501,10 @@ private fun getActionItems( showLatestButton: Boolean, showFilterButton: Boolean, onToggleFiltersClick: () -> Unit, - onClickMode: () -> Unit + onClickMode: () -> Unit, + openDisplayModeSelect: () -> Unit ): List { return listOfNotNull( - if (isConfigurable) { - ActionItem( - name = stringResource(MR.strings.location_settings), - icon = Icons.Rounded.Settings, - doAction = onSourceSettingsClick - ) - } else null, if (showFilterButton) { ActionItem( name = stringResource(MR.strings.filter_source), @@ -469,6 +529,18 @@ private fun getActionItems( }, doAction = onClickMode ) - } else null + } else null, + ActionItem( + name = stringResource(MR.strings.display_mode), + icon = Icons.Rounded.ViewModule, + doAction = openDisplayModeSelect + ), + if (isConfigurable) { + ActionItem( + name = stringResource(MR.strings.location_settings), + icon = Icons.Rounded.Settings, + doAction = onSourceSettingsClick + ) + } else null, ) }