diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/home/SourceHomeScreenViewModel.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/home/SourceHomeScreenViewModel.kt index d1f4cef0..3223fc9b 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/home/SourceHomeScreenViewModel.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/home/SourceHomeScreenViewModel.kt @@ -6,9 +6,12 @@ package ca.gosyer.jui.ui.sources.home +import androidx.compose.ui.text.intl.Locale +import ca.gosyer.jui.core.lang.displayName import ca.gosyer.jui.data.source.SourceRepositoryImpl import ca.gosyer.jui.domain.source.model.Source import ca.gosyer.jui.domain.source.service.CatalogPreferences +import ca.gosyer.jui.i18n.MR import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -37,9 +40,39 @@ class SourceHomeScreenViewModel @Inject constructor( val languages = _languages.asStateFlow() val sources = combine(installedSources, languages) { installedSources, languages -> - installedSources.filter { - it.lang in languages || it.id == Source.LOCAL_SOURCE_ID - } + val all = MR.strings.all.toPlatformString() + val other = MR.strings.other.toPlatformString() + installedSources + .distinctBy { it.id } + .filter { + it.lang in languages || it.id == Source.LOCAL_SOURCE_ID + } + .groupBy(Source::displayLang) + + .mapValues { + it.value.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, Source::name)) + .map(SourceUI::SourceItem) + } + .mapKeys { (key) -> + when (key) { + "all" -> all + "other" -> other + else -> Locale(key).displayName + } + } + .toList() + .sortedWith( + compareBy> { (key) -> + when (key) { + all -> 1 + other -> 3 + else -> 2 + } + }.thenBy(String.CASE_INSENSITIVE_ORDER, Pair::first) + ) + .flatMap { (key, value) -> + listOf(SourceUI.Header(key)) + value + } }.stateIn(scope, SharingStarted.Eagerly, emptyList()) val sourceLanguages = installedSources.map { sources -> @@ -56,10 +89,7 @@ class SourceHomeScreenViewModel @Inject constructor( private fun getSources() { sourceHandler.getSourceList() .onEach { - installedSources.value = it.sortedWith( - compareBy(String.CASE_INSENSITIVE_ORDER, Source::displayLang) - .thenBy(String.CASE_INSENSITIVE_ORDER, Source::name) - ) + installedSources.value = it _isLoading.value = false } .catch { @@ -82,3 +112,8 @@ class SourceHomeScreenViewModel @Inject constructor( private val log = logging() } } + +sealed class SourceUI { + data class Header(val header: String) : SourceUI() + data class SourceItem(val source: Source): SourceUI() +} \ No newline at end of file diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/home/components/SourceHomeScreenContent.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/home/components/SourceHomeScreenContent.kt index 0643aae6..d554efa7 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/home/components/SourceHomeScreenContent.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/sources/home/components/SourceHomeScreenContent.kt @@ -7,6 +7,7 @@ package ca.gosyer.jui.ui.sources.home.components import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -21,6 +22,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState @@ -39,9 +41,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.FilterQuality -import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import ca.gosyer.jui.domain.source.model.Source @@ -51,6 +51,7 @@ import ca.gosyer.jui.ui.base.components.localeToString import ca.gosyer.jui.ui.base.navigation.ActionItem import ca.gosyer.jui.ui.base.navigation.Toolbar import ca.gosyer.jui.ui.extensions.components.LanguageDialog +import ca.gosyer.jui.ui.sources.home.SourceUI import ca.gosyer.jui.uicore.components.LoadingScreen import ca.gosyer.jui.uicore.components.VerticalScrollbar import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter @@ -64,7 +65,7 @@ import com.vanpra.composematerialdialogs.rememberMaterialDialogState fun SourceHomeScreenContent( onAddSource: (Source) -> Unit, isLoading: Boolean, - sources: List, + sources: List, languages: Set, sourceLanguages: List, setEnabledLanguages: (Set) -> Unit, @@ -82,44 +83,15 @@ fun SourceHomeScreenContent( submitSearch = submitSearch ) } - ) { + ) { padding -> if (sources.isEmpty()) { LoadingScreen(isLoading) } else { - BoxWithConstraints(Modifier.fillMaxSize().padding(it), Alignment.TopCenter) { + BoxWithConstraints(Modifier.fillMaxSize().padding(padding), Alignment.TopCenter) { if (maxWidth > 720.dp) { - val state = rememberLazyGridState() - val cells = GridCells.Adaptive(120.dp) - LazyVerticalGrid(cells, state = state) { - items(sources) { source -> - WideSourceItem( - source, - onSourceClicked = onAddSource - ) - } - } - VerticalScrollbar( - modifier = Modifier.align(Alignment.CenterEnd) - .fillMaxHeight() - .scrollbarPadding(), - adapter = rememberVerticalScrollbarAdapter(state, cells) - ) + WideSourcesMenu(sources, onAddSource) } else { - val state = rememberLazyListState() - LazyColumn(state = state) { - items(sources) { source -> - ThinSourceItem( - source, - onSourceClicked = onAddSource - ) - } - } - VerticalScrollbar( - modifier = Modifier.align(Alignment.CenterEnd) - .fillMaxHeight() - .scrollbarPadding(), - adapter = rememberScrollbarAdapter(state) - ) + ThinSourcesMenu(sources, onAddSource) } } } @@ -151,6 +123,58 @@ fun SourceHomeScreenToolbar( ) } +@Composable +fun WideSourcesMenu( + sources: List, + onAddSource: (Source) -> Unit +) { + Box { + val state = rememberLazyGridState() + val cells = GridCells.Adaptive(120.dp) + LazyVerticalGrid(cells, state = state, modifier = Modifier.fillMaxSize()) { + items( + sources, + contentType = { + when (it) { + is SourceUI.Header -> "header" + is SourceUI.SourceItem -> "source" + } + }, + key = { + when (it) { + is SourceUI.Header -> it.header + is SourceUI.SourceItem -> it.source.id + } + }, + span = { + when (it) { + is SourceUI.Header -> GridItemSpan(maxLineSpan) + is SourceUI.SourceItem -> GridItemSpan(1) + } + } + ) { sourceUI -> + when (sourceUI) { + is SourceUI.Header -> Text( + sourceUI.header, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) + ) + is SourceUI.SourceItem -> WideSourceItem( + sourceUI.source, + onSourceClicked = onAddSource + ) + } + + } + } + VerticalScrollbar( + modifier = Modifier.align(Alignment.CenterEnd) + .fillMaxHeight() + .scrollbarPadding(), + adapter = rememberVerticalScrollbarAdapter(state, cells) + ) + } +} + @Composable fun WideSourceItem( source: Source, @@ -182,7 +206,7 @@ fun WideSourceItem( ) Spacer(Modifier.height(4.dp)) Text( - "${source.name} (${source.displayLang.toUpperCase(Locale.current)})", + source.name, color = MaterialTheme.colors.onBackground, maxLines = 2, overflow = TextOverflow.Ellipsis @@ -190,6 +214,50 @@ fun WideSourceItem( } } } +@Composable +fun ThinSourcesMenu( + sources: List, + onAddSource: (Source) -> Unit +) { + Box { + val state = rememberLazyListState() + LazyColumn(state = state, modifier = Modifier.fillMaxSize()) { + items( + sources, + contentType = { + when (it) { + is SourceUI.Header -> "header" + is SourceUI.SourceItem -> "source" + } + }, + key = { + when (it) { + is SourceUI.Header -> it.header + is SourceUI.SourceItem -> it.source.id + } + } + ) { sourceUI -> + when (sourceUI) { + is SourceUI.Header -> Text( + sourceUI.header, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) + ) + is SourceUI.SourceItem -> ThinSourceItem( + sourceUI.source, + onSourceClicked = onAddSource + ) + } + + } + } + VerticalScrollbar( + modifier = Modifier.align(Alignment.CenterEnd) + .fillMaxHeight() + .scrollbarPadding(), + adapter = rememberScrollbarAdapter(state) + ) + } +} @Composable fun ThinSourceItem(