Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions app/src/main/java/dev/sebaubuntu/athena/AthenaApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@
package dev.sebaubuntu.athena

import android.app.Application
import android.content.Context
import android.content.res.Configuration
import com.google.android.material.color.DynamicColors
import dev.sebaubuntu.athena.models.Preference
import dev.sebaubuntu.athena.repositories.PreferencesRepository
import dev.sebaubuntu.athena.utils.ModulesManager
import dev.sebaubuntu.athena.utils.PreferencesManager
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.runBlocking
import java.util.Locale

class AthenaApplication : Application() {
val coroutineScope = MainScope()
Expand All @@ -26,4 +31,47 @@ class AthenaApplication : Application() {

DynamicColors.applyToActivitiesIfAvailable(this)
}

override fun attachBaseContext(base: Context) {
val languageTag = try {
runBlocking {
preferencesManager.getValue(
Preference.Companion.primitivePreference("language", "system")
)
}
} catch (e: Exception) {
"system"
}

savedLanguageTag = languageTag

super.attachBaseContext(
wrapContextWithLocale(base, languageTag)
)
}

companion object {
@Volatile
var savedLanguageTag: String = "system"

fun wrapContextWithLocale(context: Context, languageTag: String): Context {
if (languageTag == "system") return context

val config = Configuration(context.resources.configuration)
config.setLocale(localeFromTag(languageTag))
return context.createConfigurationContext(config)
}

private fun localeFromTag(tag: String): Locale {
return when (tag) {
"id" -> Locale("in", "ID")
"he" -> Locale("iw", "IL")
"zh-CN" -> Locale("zh", "CN")
"zh-TW" -> Locale("zh", "TW")
"pt-BR" -> Locale("pt", "BR")
"pt-PT" -> Locale("pt", "PT")
else -> Locale(tag)
}
}
}
}
9 changes: 9 additions & 0 deletions app/src/main/java/dev/sebaubuntu/athena/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package dev.sebaubuntu.athena

import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
Expand All @@ -17,6 +18,14 @@ import dev.sebaubuntu.athena.utils.PermissionsManager
class MainActivity : ComponentActivity() {
private val permissionsManager = PermissionsManager(this)

override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(
AthenaApplication.wrapContextWithLocale(
newBase, AthenaApplication.savedLanguageTag
)
)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,10 @@ class PreferencesRepository(
true,
).asPreferenceHolder()

val language = primitivePreference(
"language",
"system",
).asPreferenceHolder()

private fun <T> Preference<T>.asPreferenceHolder() = PreferenceHolder(this)
}
160 changes: 159 additions & 1 deletion app/src/main/java/dev/sebaubuntu/athena/ui/screens/SettingsScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,61 @@ package dev.sebaubuntu.athena.ui.screens

import android.content.Intent
import android.os.Build
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import dev.sebaubuntu.athena.AthenaApplication
import dev.sebaubuntu.athena.R
import dev.sebaubuntu.athena.core.models.Result
import dev.sebaubuntu.athena.models.Theme
Expand All @@ -54,6 +72,31 @@ import dev.sebaubuntu.athena.ui.composables.PreferenceCategoryCard
import dev.sebaubuntu.athena.ui.composables.PreferenceListItem
import dev.sebaubuntu.athena.ui.composables.SwitchPreferenceListItem
import dev.sebaubuntu.athena.viewmodels.SettingsViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Locale

private val languageTags = listOf(
"system", "af", "ar", "ca", "cs", "da", "de", "el", "en", "eo", "es",
"fa", "fi", "fr", "hi", "hu", "id", "it", "he", "ja", "ko", "nl", "no",
"pl", "pt-BR", "pt-PT", "ro", "ru", "sr", "sv", "ta", "tr", "uk", "vi",
"zh-CN", "zh-TW"
)

private fun languageDisplayName(tag: String): String {
if (tag == "system") return "System default"
val locale = when (tag) {
"id" -> Locale("in", "ID")
"he" -> Locale("iw", "IL")
"zh-CN" -> Locale("zh", "CN")
"zh-TW" -> Locale("zh", "TW")
"pt-BR" -> Locale("pt", "BR")
"pt-PT" -> Locale("pt", "PT")
else -> Locale(tag)
}
return locale.getDisplayName(locale)
}

/**
* App settings screen.
Expand Down Expand Up @@ -84,6 +127,11 @@ fun SettingsScreen(
PreferenceCategoryCard(
titleStringResId = R.string.settings_general,
) {
LanguagePreferenceListItem(
preferenceHolder = settingsViewModel.language,
onPreferenceChange = settingsViewModel::setPreferenceValue,
)

EnumPreferenceListItem(
preferenceHolder = settingsViewModel.theme,
onPreferenceChange = settingsViewModel::setPreferenceValue,
Expand Down Expand Up @@ -122,6 +170,116 @@ fun SettingsScreen(
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun LanguagePreferenceListItem(
preferenceHolder: dev.sebaubuntu.athena.repositories.PreferencesRepository.PreferenceHolder<String>,
onPreferenceChange: (dev.sebaubuntu.athena.repositories.PreferencesRepository.PreferenceHolder<String>, String) -> Unit,
) {
val currentTag by preferenceHolder.collectAsStateWithLifecycle("system")
var dialogOpened by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
val context = LocalContext.current

if (dialogOpened) {
var selectedTag by remember { mutableStateOf(currentTag) }

BasicAlertDialog(
onDismissRequest = { dialogOpened = false }
) {
Surface(
modifier = Modifier
.fillMaxWidth(0.9f)
.heightIn(max = 500.dp),
shape = MaterialTheme.shapes.large,
tonalElevation = AlertDialogDefaults.TonalElevation,
) {
Column(
modifier = Modifier.fillMaxSize()
) {
Text(
text = "Language",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(20.dp)
)

LazyColumn(
modifier = Modifier.weight(1f),
) {
items(languageTags) { tag ->
Row(
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
.selectable(
selected = (tag == selectedTag),
onClick = { selectedTag = tag },
role = Role.RadioButton
)
.padding(horizontal = 20.dp),
verticalAlignment = Alignment.CenterVertically,
) {
RadioButton(
selected = tag == selectedTag,
onClick = null,
)
Text(
text = languageDisplayName(tag),
modifier = Modifier.padding(start = 16.dp),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.bodyLarge,
)
}
}
}

Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.End
) {
TextButton(
onClick = { dialogOpened = false }
) {
Text(stringResource(android.R.string.cancel))
}
TextButton(
onClick = {
if (selectedTag != currentTag) {
scope.launch(Dispatchers.IO) {
preferenceHolder.setValue(selectedTag)
AthenaApplication.savedLanguageTag = selectedTag
withContext(Dispatchers.Main) {
(context as ComponentActivity).recreate()
}
}
}
dialogOpened = false
}
) {
Text(stringResource(android.R.string.ok))
}
}
}
}
}
}

ListItem(
headlineContent = {
Text(text = "Language")
},
supportingContent = {
Text(text = languageDisplayName(currentTag))
},
modifier = Modifier.clickable { dialogOpened = true },
colors = ListItemDefaults.colors(
containerColor = Color.Transparent,
),
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ExportDataCard(
Expand Down Expand Up @@ -329,4 +487,4 @@ private fun AboutLinkIconButton(
contentDescription = stringResource(nameStringResId),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class SettingsViewModel(
// General
val theme = preferencesRepository.theme
val dynamicColors = preferencesRepository.dynamicColors
val language = preferencesRepository.language

// Export data
private val _exportDataStatus = MutableSharedFlow<ExportDataStatus?>()
Expand Down
Loading