mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-20 11:32:31 +01:00
Sources menu improvements
This commit is contained in:
@@ -6,9 +6,12 @@
|
|||||||
|
|
||||||
package ca.gosyer.jui.ui.sources.home
|
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.data.source.SourceRepositoryImpl
|
||||||
import ca.gosyer.jui.domain.source.model.Source
|
import ca.gosyer.jui.domain.source.model.Source
|
||||||
import ca.gosyer.jui.domain.source.service.CatalogPreferences
|
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.ContextWrapper
|
||||||
import ca.gosyer.jui.uicore.vm.ViewModel
|
import ca.gosyer.jui.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -37,9 +40,39 @@ class SourceHomeScreenViewModel @Inject constructor(
|
|||||||
val languages = _languages.asStateFlow()
|
val languages = _languages.asStateFlow()
|
||||||
|
|
||||||
val sources = combine(installedSources, languages) { installedSources, languages ->
|
val sources = combine(installedSources, languages) { installedSources, languages ->
|
||||||
installedSources.filter {
|
val all = MR.strings.all.toPlatformString()
|
||||||
it.lang in languages || it.id == Source.LOCAL_SOURCE_ID
|
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<Pair<String, *>> { (key) ->
|
||||||
|
when (key) {
|
||||||
|
all -> 1
|
||||||
|
other -> 3
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
}.thenBy(String.CASE_INSENSITIVE_ORDER, Pair<String, *>::first)
|
||||||
|
)
|
||||||
|
.flatMap { (key, value) ->
|
||||||
|
listOf(SourceUI.Header(key)) + value
|
||||||
|
}
|
||||||
}.stateIn(scope, SharingStarted.Eagerly, emptyList())
|
}.stateIn(scope, SharingStarted.Eagerly, emptyList())
|
||||||
|
|
||||||
val sourceLanguages = installedSources.map { sources ->
|
val sourceLanguages = installedSources.map { sources ->
|
||||||
@@ -56,10 +89,7 @@ class SourceHomeScreenViewModel @Inject constructor(
|
|||||||
private fun getSources() {
|
private fun getSources() {
|
||||||
sourceHandler.getSourceList()
|
sourceHandler.getSourceList()
|
||||||
.onEach {
|
.onEach {
|
||||||
installedSources.value = it.sortedWith(
|
installedSources.value = it
|
||||||
compareBy(String.CASE_INSENSITIVE_ORDER, Source::displayLang)
|
|
||||||
.thenBy(String.CASE_INSENSITIVE_ORDER, Source::name)
|
|
||||||
)
|
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
.catch {
|
.catch {
|
||||||
@@ -82,3 +112,8 @@ class SourceHomeScreenViewModel @Inject constructor(
|
|||||||
private val log = logging()
|
private val log = logging()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class SourceUI {
|
||||||
|
data class Header(val header: String) : SourceUI()
|
||||||
|
data class SourceItem(val source: Source): SourceUI()
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
package ca.gosyer.jui.ui.sources.home.components
|
package ca.gosyer.jui.ui.sources.home.components
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
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.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
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.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
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.Modifier
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.FilterQuality
|
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.style.TextOverflow
|
||||||
import androidx.compose.ui.text.toUpperCase
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import ca.gosyer.jui.domain.source.model.Source
|
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.ActionItem
|
||||||
import ca.gosyer.jui.ui.base.navigation.Toolbar
|
import ca.gosyer.jui.ui.base.navigation.Toolbar
|
||||||
import ca.gosyer.jui.ui.extensions.components.LanguageDialog
|
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.LoadingScreen
|
||||||
import ca.gosyer.jui.uicore.components.VerticalScrollbar
|
import ca.gosyer.jui.uicore.components.VerticalScrollbar
|
||||||
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
|
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
|
||||||
@@ -64,7 +65,7 @@ import com.vanpra.composematerialdialogs.rememberMaterialDialogState
|
|||||||
fun SourceHomeScreenContent(
|
fun SourceHomeScreenContent(
|
||||||
onAddSource: (Source) -> Unit,
|
onAddSource: (Source) -> Unit,
|
||||||
isLoading: Boolean,
|
isLoading: Boolean,
|
||||||
sources: List<Source>,
|
sources: List<SourceUI>,
|
||||||
languages: Set<String>,
|
languages: Set<String>,
|
||||||
sourceLanguages: List<String>,
|
sourceLanguages: List<String>,
|
||||||
setEnabledLanguages: (Set<String>) -> Unit,
|
setEnabledLanguages: (Set<String>) -> Unit,
|
||||||
@@ -82,44 +83,15 @@ fun SourceHomeScreenContent(
|
|||||||
submitSearch = submitSearch
|
submitSearch = submitSearch
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) { padding ->
|
||||||
if (sources.isEmpty()) {
|
if (sources.isEmpty()) {
|
||||||
LoadingScreen(isLoading)
|
LoadingScreen(isLoading)
|
||||||
} else {
|
} else {
|
||||||
BoxWithConstraints(Modifier.fillMaxSize().padding(it), Alignment.TopCenter) {
|
BoxWithConstraints(Modifier.fillMaxSize().padding(padding), Alignment.TopCenter) {
|
||||||
if (maxWidth > 720.dp) {
|
if (maxWidth > 720.dp) {
|
||||||
val state = rememberLazyGridState()
|
WideSourcesMenu(sources, onAddSource)
|
||||||
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)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
val state = rememberLazyListState()
|
ThinSourcesMenu(sources, onAddSource)
|
||||||
LazyColumn(state = state) {
|
|
||||||
items(sources) { source ->
|
|
||||||
ThinSourceItem(
|
|
||||||
source,
|
|
||||||
onSourceClicked = onAddSource
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VerticalScrollbar(
|
|
||||||
modifier = Modifier.align(Alignment.CenterEnd)
|
|
||||||
.fillMaxHeight()
|
|
||||||
.scrollbarPadding(),
|
|
||||||
adapter = rememberScrollbarAdapter(state)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,6 +123,58 @@ fun SourceHomeScreenToolbar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WideSourcesMenu(
|
||||||
|
sources: List<SourceUI>,
|
||||||
|
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
|
@Composable
|
||||||
fun WideSourceItem(
|
fun WideSourceItem(
|
||||||
source: Source,
|
source: Source,
|
||||||
@@ -182,7 +206,7 @@ fun WideSourceItem(
|
|||||||
)
|
)
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
"${source.name} (${source.displayLang.toUpperCase(Locale.current)})",
|
source.name,
|
||||||
color = MaterialTheme.colors.onBackground,
|
color = MaterialTheme.colors.onBackground,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
@@ -190,6 +214,50 @@ fun WideSourceItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Composable
|
||||||
|
fun ThinSourcesMenu(
|
||||||
|
sources: List<SourceUI>,
|
||||||
|
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
|
@Composable
|
||||||
fun ThinSourceItem(
|
fun ThinSourceItem(
|
||||||
|
|||||||
Reference in New Issue
Block a user