feat(settings): language picker

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2024-11-14 22:57:02 +01:00
parent 4e29fba724
commit 1b4080520a
7 changed files with 237 additions and 2 deletions

View File

@ -147,6 +147,15 @@
android:value="" />
</service>
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
</application>
</manifest>

View File

@ -9,6 +9,7 @@ import android.util.Log
import android.widget.Toast
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.core.EaseInOutExpo
import androidx.compose.animation.core.FiniteAnimationSpec
@ -30,7 +31,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.compose.NavHost
@ -72,6 +72,7 @@ import chat.revolt.screens.settings.ChangelogsSettingsScreen
import chat.revolt.screens.settings.ChatSettingsScreen
import chat.revolt.screens.settings.DebugSettingsScreen
import chat.revolt.screens.settings.ExperimentsSettingsScreen
import chat.revolt.screens.settings.LanguagePickerSettingsScreen
import chat.revolt.screens.settings.ProfileSettingsScreen
import chat.revolt.screens.settings.SessionSettingsScreen
import chat.revolt.screens.settings.SettingsScreen
@ -256,7 +257,7 @@ class MainActivityViewModel @Inject constructor(
}
@AndroidEntryPoint
class MainActivity : FragmentActivity() {
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<MainActivityViewModel>()
// Fix for SDK >=31, where core-splashscreen accidentally removes dynamic colours
@ -454,6 +455,7 @@ fun AppEntrypoint(
composable("settings/debug") { DebugSettingsScreen(navController) }
composable("settings/experiments") { ExperimentsSettingsScreen(navController) }
composable("settings/changelogs") { ChangelogsSettingsScreen(navController) }
composable("settings/language") { LanguagePickerSettingsScreen(navController) }
composable("settings/channel/{channelId}") { backStackEntry ->
val channelId = backStackEntry.arguments?.getString("channelId") ?: ""

View File

@ -0,0 +1,27 @@
package chat.revolt.internals.extensions
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.XmlResourceParser
import java.util.Locale
@SuppressLint("DiscouragedApi")
fun Context.getSupportedLocales(): List<Locale> {
val id = resources.getIdentifier(
"_generated_res_locale_config",
"xml",
packageName
)
val localeXml = resources.getXml(id)
val locales = ArrayList<String>()
var event = localeXml.next()
while (event != XmlResourceParser.END_DOCUMENT) {
if (event == XmlResourceParser.START_TAG && localeXml.name == "locale") {
locales.add(localeXml.getAttributeValue(0))
}
event = localeXml.next()
}
return locales.map {
Locale.forLanguageTag(it)
}
}

View File

@ -0,0 +1,166 @@
package chat.revolt.screens.settings
import android.content.Context
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.clickable
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.ListItem
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import chat.revolt.R
import chat.revolt.internals.extensions.getSupportedLocales
import java.util.Locale
class LanguagePickerSettingsScreen : ViewModel() {
val locales = mutableStateListOf<Locale>()
var currentLocale by mutableStateOf<Locale?>(null)
fun initLanguages(context: Context) {
locales.clear()
locales.addAll(context.getSupportedLocales())
currentLocale = determineCurrentlySelectedLocale()
}
private fun determineCurrentlySelectedLocale(): Locale? {
return AppCompatDelegate.getApplicationLocales()[0]
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LanguagePickerSettingsScreen(
navController: NavController,
viewModel: LanguagePickerSettingsScreen = viewModel()
) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val context = LocalContext.current
LaunchedEffect(Unit) {
viewModel.initLanguages(context)
}
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
stringResource(id = R.string.settings_language),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = {
navController?.let {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
}
},
)
},
) { pv ->
LazyColumn(
contentPadding = pv,
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
item(key = "auto") {
ListItem(
headlineContent = {
Text(
stringResource(id = R.string.settings_language_auto),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
leadingContent = {
RadioButton(
selected = viewModel.currentLocale == null,
onClick = null
)
},
modifier = Modifier.clickable {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())
viewModel.currentLocale = null
}
)
}
items(
viewModel.locales.size,
key = { index -> viewModel.locales[index].toLanguageTag() }
) { index ->
val locale = viewModel.locales[index]
ListItem(
headlineContent = {
Text(
locale.displayLanguage,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
supportingContent = {
if (!locale.displayCountry.isNullOrEmpty()) {
Text(
locale.displayCountry,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
},
trailingContent = {
Text(
locale.getDisplayLanguage(locale),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
leadingContent = {
RadioButton(
selected = locale.toLanguageTag() == viewModel.currentLocale?.toLanguageTag(),
onClick = null
)
},
modifier = Modifier
.clickable {
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags(
locale.toLanguageTag()
)
)
viewModel.currentLocale = locale
}
)
}
}
}
}

View File

@ -174,6 +174,25 @@ fun SettingsScreen(
}
)
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.settings_language)
)
},
leadingContent = {
Icon(
painter = painterResource(R.drawable.ic_earth_24dp),
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_language")
.clickable {
navController.navigate("settings/language")
}
)
ListItem(
headlineContent = {
Text(

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
</vector>

View File

@ -655,6 +655,9 @@
<string name="settings_changelogs_historical_version_header">Changelog for %1$s</string>
<string name="settings_changelogs_historical_version_header_placeholder">that version</string>
<string name="settings_language">Language</string>
<string name="settings_language_auto">System default</string>
<string name="channel_settings">Channel Settings</string>
<string name="channel_settings_header">#%1$s</string>
<string name="channel_settings_overview">Overview</string>