From 3a8a50006d2a0cd985352568024cd0c1ff8e6159 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 14 Aug 2023 21:25:12 -0400 Subject: [PATCH] Improvements to the Settings Interface --- .../amethyst/ui/components/TextSpinner.kt | 4 +- .../amethyst/ui/navigation/AppTopBar.kt | 29 +- .../amethyst/ui/screen/loggedIn/MainScreen.kt | 9 +- .../ui/screen/loggedIn/SettingsScreen.kt | 248 ++++++++++-------- app/src/main/res/values/strings.xml | 16 +- app/src/main/res/xml/locales_config.xml | 6 +- 6 files changed, 172 insertions(+), 140 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt index e09fa5cde..afa004ddd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt @@ -35,7 +35,7 @@ import kotlinx.collections.immutable.ImmutableList @Composable fun TextSpinner( - label: String, + label: String?, placeholder: String, options: ImmutableList, explainers: ImmutableList? = null, @@ -54,7 +54,7 @@ fun TextSpinner( value = currentText, onValueChange = {}, readOnly = true, - label = { Text(label) }, + label = { label?.let { Text(it) } }, modifier = Modifier .fillMaxWidth() .focusRequester(focusRequester) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index f5dcb29a1..2c5de3a7b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -44,10 +44,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavHostController import coil.Coil import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account @@ -108,7 +106,8 @@ fun AppTopBar( navEntryState: State, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, - nav: (String) -> Unit + nav: (String) -> Unit, + navPopBack: () -> Unit ) { val currentRoute by remember(navEntryState.value) { derivedStateOf { @@ -122,7 +121,7 @@ fun AppTopBar( } } - RenderTopRouteBar(currentRoute, id, followLists, scaffoldState, accountViewModel, nav) + RenderTopRouteBar(currentRoute, id, followLists, scaffoldState, accountViewModel, nav, navPopBack) } @Composable @@ -132,14 +131,15 @@ private fun RenderTopRouteBar( followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, - nav: (String) -> Unit + nav: (String) -> Unit, + navPopBack: () -> Unit ) { when (currentRoute) { - // Route.Profile.route -> TopBarWithBackButton(nav) Route.Home.base -> HomeTopBar(followLists, scaffoldState, accountViewModel, nav) Route.Video.base -> StoriesTopBar(followLists, scaffoldState, accountViewModel, nav) Route.Discover.base -> DiscoveryTopBar(followLists, scaffoldState, accountViewModel, nav) Route.Notification.base -> NotificationTopBar(followLists, scaffoldState, accountViewModel, nav) + Route.Settings.base -> TopBarWithBackButton(stringResource(id = R.string.application_preferences), navPopBack) else -> { if (id != null) { when (currentRoute) { @@ -488,24 +488,23 @@ fun SimpleTextSpinner( } @Composable -fun TopBarWithBackButton(navController: NavHostController) { +fun TopBarWithBackButton(caption: String, popBack: () -> Unit) { Column() { TopAppBar( elevation = 0.dp, backgroundColor = Color(0xFFFFFF), - title = {}, + title = { + Text(caption) + }, navigationIcon = { IconButton( - onClick = { - navController.popBackStack() - }, + onClick = popBack, modifier = Modifier ) { Icon( - imageVector = Icons.Filled.ArrowBack, - null, - modifier = Modifier.size(28.dp), - tint = MaterialTheme.colors.primary + imageVector = Icons.Default.ArrowBack, + contentDescription = stringResource(R.string.back), + tint = MaterialTheme.colors.onSurface ) } }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt index 0341b9e17..b6a9e7b83 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt @@ -95,6 +95,13 @@ fun MainScreen( } } + val navPopBack = remember(navController) { + { + navController.popBackStack() + Unit + } + } + val followLists: FollowListViewModel = viewModel( key = accountViewModel.userProfile().pubkeyHex + "FollowListViewModel", factory = FollowListViewModel.Factory(accountViewModel.account) @@ -201,7 +208,7 @@ fun MainScreen( AppBottomBar(accountViewModel, navState, navBottomRow) }, topBar = { - AppTopBar(followLists, navState, scaffoldState, accountViewModel, nav = nav) + AppTopBar(followLists, navState, scaffoldState, accountViewModel, nav = nav, navPopBack) }, drawerContent = { DrawerContent(nav, scaffoldState, sheetState, accountViewModel) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt index c7108ae65..e7e18bc5a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt @@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn import android.content.Context import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -15,6 +16,7 @@ import androidx.compose.material.DropdownMenuItem import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExposedDropdownMenuBox import androidx.compose.material.ExposedDropdownMenuDefaults +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.runtime.Composable @@ -26,10 +28,12 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.os.LocaleListCompat @@ -39,7 +43,10 @@ import com.vitorpamplona.amethyst.model.ConnectivityType import com.vitorpamplona.amethyst.model.parseConnectivityType import com.vitorpamplona.amethyst.ui.screen.ThemeViewModel import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer -import com.vitorpamplona.amethyst.ui.theme.StdPadding +import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer +import com.vitorpamplona.amethyst.ui.theme.Size10dp +import com.vitorpamplona.amethyst.ui.theme.Size20dp +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.DelicateCoroutinesApi @@ -108,16 +115,18 @@ fun SettingsScreen( stringResource(ConnectivityType.WIFI_ONLY.reourceId), stringResource(ConnectivityType.NEVER.reourceId) ) - val settings = accountViewModel.account.settings - val index = settings.automaticallyShowImages.screenCode - val videoIndex = settings.automaticallyStartPlayback.screenCode - val linkIndex = settings.automaticallyShowUrlPreview.screenCode val themeItens = persistentListOf( stringResource(R.string.system), stringResource(R.string.light), stringResource(R.string.dark) ) + + val settings = accountViewModel.account.settings + val showImagesIndex = settings.automaticallyShowImages.screenCode + val videoIndex = settings.automaticallyStartPlayback.screenCode + val linkIndex = settings.automaticallyShowUrlPreview.screenCode + val themeIndex = themeViewModel.theme.value ?: 0 val context = LocalContext.current @@ -127,123 +136,132 @@ fun SettingsScreen( val languageIndex = getLanguageIndex(languageEntries) Column( - StdPadding + Modifier + .padding(top = Size10dp, start = Size20dp, end = Size20dp) .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { - Section(stringResource(R.string.application_preferences)) - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() + SettingsRow( + R.string.language, + R.string.language_description, + languageList, + languageIndex ) { - TextSpinner( - label = stringResource(R.string.language), - placeholder = languageList[languageIndex], - options = languageList, - onSelect = { - GlobalScope.launch(Dispatchers.Main) { - val job = scope.launch(Dispatchers.IO) { - val locale = languageEntries[languageList[it]] - accountViewModel.account.settings.preferredLanguage = locale - LocalPreferences.saveToEncryptedStorage(accountViewModel.account) - } - job.join() - val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(languageEntries[languageList[it]]) - AppCompatDelegate.setApplicationLocales(appLocale) - } - }, - modifier = Modifier - .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) - .weight(1f) + GlobalScope.launch(Dispatchers.Main) { + val job = scope.launch(Dispatchers.IO) { + val locale = languageEntries[languageList[it]] + accountViewModel.account.settings.preferredLanguage = locale + LocalPreferences.saveToEncryptedStorage(accountViewModel.account) + } + job.join() + val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(languageEntries[languageList[it]]) + AppCompatDelegate.setApplicationLocales(appLocale) + } + } + + Spacer(modifier = HalfVertSpacer) + + SettingsRow( + R.string.theme, + R.string.theme_description, + themeItens, + themeIndex + ) { + themeViewModel.onChange(it) + scope.launch(Dispatchers.IO) { + LocalPreferences.updateTheme(it) + } + } + + Spacer(modifier = HalfVertSpacer) + + SettingsRow( + R.string.automatically_load_images_gifs, + R.string.automatically_load_images_gifs_description, + selectedItens, + showImagesIndex + ) { + val automaticallyShowImages = parseConnectivityType(it) + + scope.launch(Dispatchers.IO) { + accountViewModel.updateAutomaticallyShowImages(automaticallyShowImages) + LocalPreferences.saveToEncryptedStorage(accountViewModel.account) + } + } + + Spacer(modifier = HalfVertSpacer) + + SettingsRow( + R.string.automatically_play_videos, + R.string.automatically_play_videos_description, + selectedItens, + videoIndex + ) { + val automaticallyStartPlayback = parseConnectivityType(it) + + scope.launch(Dispatchers.IO) { + accountViewModel.updateAutomaticallyStartPlayback(automaticallyStartPlayback) + LocalPreferences.saveToEncryptedStorage(accountViewModel.account) + } + } + + Spacer(modifier = HalfVertSpacer) + + SettingsRow( + R.string.automatically_show_url_preview, + R.string.automatically_show_url_preview_description, + selectedItens, + linkIndex + ) { + val automaticallyShowUrlPreview = parseConnectivityType(it) + + scope.launch(Dispatchers.IO) { + accountViewModel.updateAutomaticallyShowUrlPreview(automaticallyShowUrlPreview) + LocalPreferences.saveToEncryptedStorage(accountViewModel.account) + } + } + } +} + +@Composable +fun SettingsRow( + name: Int, + description: Int, + selectedItens: ImmutableList, + selectedIndex: Int, + onSelect: (Int) -> Unit +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.weight(2.0f), + verticalArrangement = Arrangement.spacedBy(3.dp) + ) { + Text( + text = stringResource(name), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = stringResource(description), + style = MaterialTheme.typography.caption, + color = Color.Gray, + maxLines = 2, + overflow = TextOverflow.Ellipsis ) } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - TextSpinner( - label = stringResource(R.string.theme), - placeholder = themeItens[themeIndex], - options = themeItens, - onSelect = { - themeViewModel.onChange(it) - scope.launch(Dispatchers.IO) { - LocalPreferences.updateTheme(it) - } - }, - modifier = Modifier - .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) - .weight(1f) - ) - } - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - TextSpinner( - label = stringResource(R.string.automatically_load_images_gifs), - placeholder = selectedItens[index], - options = selectedItens, - onSelect = { - val automaticallyShowImages = parseConnectivityType(it) - - scope.launch(Dispatchers.IO) { - accountViewModel.updateAutomaticallyShowImages(automaticallyShowImages) - LocalPreferences.saveToEncryptedStorage(accountViewModel.account) - } - }, - modifier = Modifier - .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) - .weight(1f) - ) - } - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - TextSpinner( - label = stringResource(R.string.automatically_play_videos), - placeholder = selectedItens[videoIndex], - options = selectedItens, - onSelect = { - val automaticallyStartPlayback = parseConnectivityType(it) - - scope.launch(Dispatchers.IO) { - accountViewModel.updateAutomaticallyStartPlayback(automaticallyStartPlayback) - LocalPreferences.saveToEncryptedStorage(accountViewModel.account) - } - }, - modifier = Modifier - .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) - .weight(1f) - ) - } - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - TextSpinner( - label = stringResource(R.string.automatically_show_url_preview), - placeholder = selectedItens[linkIndex], - options = selectedItens, - onSelect = { - val automaticallyShowUrlPreview = parseConnectivityType(it) - - scope.launch(Dispatchers.IO) { - accountViewModel.updateAutomaticallyShowUrlPreview(automaticallyShowUrlPreview) - LocalPreferences.saveToEncryptedStorage(accountViewModel.account) - } - }, - modifier = Modifier - .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) - .weight(1f) - ) - } + TextSpinner( + label = "", + placeholder = selectedItens[selectedIndex], + options = selectedItens, + onSelect = onSelect, + modifier = Modifier + .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) + .weight(1f) + ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 517dbe3e3..4f9bf83ac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -493,12 +493,12 @@ System Light Dark - Application preferences + Application Preferences Language Theme - Automatically load images/gifs - Automatically play videos - Automatically show url preview + Image Preview + Video Playback + URL Preview Load Image Spammers @@ -536,4 +536,12 @@ Members of this group Explanation to members Changing the name for the new goals. + + For the App\'s Interface + Dark, Light or System theme + Automatically load images and GIFs + Automatically plays videos and GIFs + Show URL previews + When to load images + diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index a407a07dd..64d933c4f 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -4,10 +4,13 @@ + + + @@ -18,7 +21,4 @@ - - -