diff --git a/src/main/kotlin/ca/gosyer/data/extension/ExtensionPreferences.kt b/src/main/kotlin/ca/gosyer/data/extension/ExtensionPreferences.kt index 90d146dc..2ca3c640 100644 --- a/src/main/kotlin/ca/gosyer/data/extension/ExtensionPreferences.kt +++ b/src/main/kotlin/ca/gosyer/data/extension/ExtensionPreferences.kt @@ -8,9 +8,10 @@ package ca.gosyer.data.extension import ca.gosyer.common.prefs.Preference import ca.gosyer.common.prefs.PreferenceStore +import java.util.Locale class ExtensionPreferences(private val preferenceStore: PreferenceStore) { fun languages(): Preference> { - return preferenceStore.getStringSet("enabled_langs", setOf("all", "en")) + return preferenceStore.getStringSet("enabled_langs", setOf("all", "en", Locale.getDefault().language)) } } diff --git a/src/main/kotlin/ca/gosyer/ui/extensions/ExtensionsMenu.kt b/src/main/kotlin/ca/gosyer/ui/extensions/ExtensionsMenu.kt index 05f0c37b..2972574e 100644 --- a/src/main/kotlin/ca/gosyer/ui/extensions/ExtensionsMenu.kt +++ b/src/main/kotlin/ca/gosyer/ui/extensions/ExtensionsMenu.kt @@ -6,6 +6,7 @@ package ca.gosyer.ui.extensions +import androidx.compose.foundation.ScrollbarAdapter import androidx.compose.foundation.VerticalScrollbar import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -21,15 +22,20 @@ 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 import androidx.compose.foundation.rememberScrollbarAdapter import androidx.compose.material.Button import androidx.compose.material.ContentAlpha import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.compose.material.Switch import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Translate import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -41,12 +47,15 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import ca.gosyer.BuildConfig import ca.gosyer.data.models.Extension +import ca.gosyer.ui.base.WindowDialog +import ca.gosyer.ui.base.components.ActionIcon import ca.gosyer.ui.base.components.KtorImage import ca.gosyer.ui.base.components.LoadingScreen import ca.gosyer.ui.base.components.Toolbar import ca.gosyer.ui.base.vm.viewModel import ca.gosyer.util.compose.ThemedWindow import ca.gosyer.util.compose.persistentLazyListState +import kotlinx.coroutines.flow.MutableStateFlow import java.util.Locale fun openExtensionsMenu() { @@ -78,6 +87,18 @@ fun ExtensionsMenu() { searchText = search, search = { vm.search(it) + }, + actions = { + ActionIcon( + { + val enabledLangs = MutableStateFlow(vm.enabledLangs.value) + LanguageDialog(enabledLangs, vm.getSourceLanguages().toList()) { + vm.setEnabledLanguages(enabledLangs.value) + } + }, + "Enabled languages", + Icons.Default.Translate + ) } ) } @@ -154,3 +175,34 @@ fun ExtensionItem( } } } + +fun LanguageDialog(enabledLangsFlow: MutableStateFlow>, availableLangs: List, setLangs: () -> Unit) { + WindowDialog(BuildConfig.NAME, onPositiveButton = setLangs) { + val enabledLangs by enabledLangsFlow.collectAsState() + val state = rememberLazyListState() + Box { + LazyColumn(Modifier.fillMaxWidth(), state) { + items(availableLangs) { lang -> + Row { + val langName = remember(lang) { + Locale.forLanguageTag(lang)?.displayName ?: lang + } + Text(langName) + Switch( + lang in enabledLangs, + { + if (it) { + enabledLangsFlow.value += lang + } else { + enabledLangsFlow.value -= lang + } + } + ) + } + } + item { Spacer(Modifier.height(70.dp)) } + } + VerticalScrollbar(ScrollbarAdapter(state), Modifier.align(Alignment.CenterEnd).padding(8.dp)) + } + } +} diff --git a/src/main/kotlin/ca/gosyer/ui/extensions/ExtensionsMenuViewModel.kt b/src/main/kotlin/ca/gosyer/ui/extensions/ExtensionsMenuViewModel.kt index fe633a00..54eae8ec 100644 --- a/src/main/kotlin/ca/gosyer/ui/extensions/ExtensionsMenuViewModel.kt +++ b/src/main/kotlin/ca/gosyer/ui/extensions/ExtensionsMenuViewModel.kt @@ -15,6 +15,9 @@ import ca.gosyer.util.lang.throwIfCancellation import ca.gosyer.util.system.CKLogger import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @@ -24,6 +27,8 @@ class ExtensionsMenuViewModel @Inject constructor( private val extensionPreferences: ExtensionPreferences ) : ViewModel() { val serverUrl = serverPreferences.serverUrl().stateIn(scope) + private val _enabledLangs = extensionPreferences.languages().asStateFlow() + val enabledLangs = _enabledLangs.asStateFlow() private lateinit var extensionList: List @@ -33,21 +38,22 @@ class ExtensionsMenuViewModel @Inject constructor( private val _isLoading = MutableStateFlow(true) val isLoading = _isLoading.asStateFlow() - var searchQuery = MutableStateFlow(null) + val searchQuery = MutableStateFlow(null) init { scope.launch { getExtensions() } + + enabledLangs.drop(1).onEach { + search(searchQuery.value.orEmpty()) + }.launchIn(scope) } private suspend fun getExtensions() { try { _isLoading.value = true - val enabledLangs = extensionPreferences.languages().get() extensionList = extensionHandler.getExtensionList() - .filter { it.lang in enabledLangs } - .sortedWith(compareBy({ it.lang }, { it.pkgName })) search(searchQuery.value.orEmpty()) } catch (e: Exception) { e.throwIfCancellation() @@ -93,8 +99,16 @@ class ExtensionsMenuViewModel @Inject constructor( } } + fun getSourceLanguages() = extensionList.map { it.lang }.toSet() + + fun setEnabledLanguages(langs: Set) { + info { langs } + _enabledLangs.value = langs + } + fun search(searchQuery: String) { this.searchQuery.value = searchQuery.takeUnless { it.isBlank() } + val extensionList = extensionList.filter { it.lang in enabledLangs.value } if (searchQuery.isBlank()) { _extensions.value = extensionList.splitSort() } else { @@ -108,10 +122,11 @@ class ExtensionsMenuViewModel @Inject constructor( } private fun List.splitSort(): List { - val obsolete = filter { it.obsolete }.sortedWith(compareBy({ it.lang }, { it.pkgName })) - val updates = filter { it.hasUpdate }.sortedWith(compareBy({ it.lang }, { it.pkgName })) - val installed = filter { it.installed && !it.hasUpdate && !it.obsolete }.sortedWith(compareBy({ it.lang }, { it.pkgName })) - val available = filter { !it.installed }.sortedWith(compareBy({ it.lang }, { it.pkgName })) + val comparator = compareBy({ it.lang }, { it.pkgName }) + val obsolete = filter { it.obsolete }.sortedWith(comparator) + val updates = filter { it.hasUpdate }.sortedWith(comparator) + val installed = filter { it.installed && !it.hasUpdate && !it.obsolete }.sortedWith(comparator) + val available = filter { !it.installed }.sortedWith(comparator) return obsolete + updates + installed + available }