Add language options

- Fix some icon color issues
This commit is contained in:
ShirkNeko
2025-05-14 15:01:59 +08:00
parent 3d0d87cb0c
commit dee7cc6f2b
7 changed files with 295 additions and 6 deletions

View File

@@ -1,17 +1,63 @@
package com.sukisu.ultra
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import coil.Coil
import coil.ImageLoader
import com.dergoogler.mmrl.platform.Platform
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import java.io.File
import java.util.Locale
lateinit var ksuApp: KernelSUApplication
class KernelSUApplication : Application() {
override fun attachBaseContext(base: Context) {
val prefs = base.getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
var context = base
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val config = Configuration(base.resources.configuration)
config.setLocale(locale)
context = base.createConfigurationContext(config)
}
super.attachBaseContext(context)
}
@SuppressLint("ObsoleteSdkInt")
override fun getResources(): Resources {
val resources = super.getResources()
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
val config = Configuration(resources.configuration)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return createConfigurationContext(config).resources
} else {
@Suppress("DEPRECATION")
resources.updateConfiguration(config, resources.displayMetrics)
}
}
return resources
}
override fun onCreate() {
super.onCreate()
ksuApp = this
@@ -35,5 +81,30 @@ class KernelSUApplication : Application() {
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
applyLanguageSetting()
}
@SuppressLint("ObsoleteSdkInt")
private fun applyLanguageSetting() {
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val resources = resources
val config = Configuration(resources.configuration)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
createConfigurationContext(config)
} else {
@Suppress("DEPRECATION")
resources.updateConfiguration(config, resources.displayMetrics)
}
}
}
}

View File

@@ -1,6 +1,8 @@
package com.sukisu.ultra.ui
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.database.ContentObserver
import android.os.Build
import android.os.Bundle
@@ -36,6 +38,7 @@ import com.sukisu.ultra.ui.util.*
import androidx.core.content.edit
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
import com.sukisu.ultra.ui.webui.initPlatform
import java.util.Locale
class MainActivity : ComponentActivity() {
private inner class ThemeChangeContentObserver(
@@ -48,8 +51,49 @@ class MainActivity : ComponentActivity() {
}
}
// 应用保存的语言设置
@SuppressLint("ObsoleteSdkInt")
private fun applyLanguageSetting() {
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val resources = resources
val config = Configuration(resources.configuration)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
createConfigurationContext(config)
} else {
@Suppress("DEPRECATION")
resources.updateConfiguration(config, resources.displayMetrics)
}
}
}
override fun attachBaseContext(newBase: Context) {
val prefs = newBase.getSharedPreferences("settings", MODE_PRIVATE)
val languageCode = prefs.getString("app_language", "") ?: ""
var context = newBase
if (languageCode.isNotEmpty()) {
val locale = Locale.forLanguageTag(languageCode)
Locale.setDefault(locale)
val config = Configuration(newBase.resources.configuration)
config.setLocale(locale)
context = newBase.createConfigurationContext(config)
}
super.attachBaseContext(context)
}
override fun onCreate(savedInstanceState: Bundle?) {
// 应用DPI设置仅对当前应用生效
// 确保应用正确的语言设置
applyLanguageSetting()
applyCustomDpi()
// Enable edge to edge
@@ -138,7 +182,7 @@ class MainActivity : ComponentActivity() {
}
}
// 应用自定义DPI设置(仅对当前应用生效)
// 应用自定义DPI设置
private fun applyCustomDpi() {
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val customDpi = prefs.getInt("app_dpi", 0)
@@ -147,9 +191,8 @@ class MainActivity : ComponentActivity() {
try {
val resources = resources
val metrics = resources.displayMetrics
// 仅更新应用内显示,不影响系统状态栏
metrics.density = customDpi / 160f
@Suppress("DEPRECATION")
metrics.scaledDensity = customDpi / 160f
metrics.densityDpi = customDpi
} catch (e: Exception) {
@@ -169,6 +212,8 @@ class MainActivity : ComponentActivity() {
override fun onResume() {
super.onResume()
applyLanguageSetting()
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
loadCustomBackground()
}
@@ -180,6 +225,11 @@ class MainActivity : ComponentActivity() {
destroyListeners.forEach { it() }
super.onDestroy()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
applyLanguageSetting()
}
}
@OptIn(ExperimentalMaterial3Api::class)

View File

@@ -5,6 +5,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
@@ -62,7 +63,8 @@ fun SwitchItem(
Icon(
modifier = Modifier.then(stateAlpha),
imageVector = icon,
contentDescription = title
contentDescription = title,
tint = MaterialTheme.colorScheme.primary
)
}
},

View File

@@ -60,7 +60,7 @@ fun SwitchItem(
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
maxLines = 1,
maxLines = Int.MAX_VALUE,
overflow = TextOverflow.Ellipsis
)
},

View File

@@ -1,7 +1,10 @@
package com.sukisu.ultra.ui.screen
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.widget.Toast
@@ -36,6 +39,7 @@ import androidx.compose.material.icons.filled.ColorLens
import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.Opacity
import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.Security
@@ -84,6 +88,7 @@ import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.MainActivity
import com.sukisu.ultra.ui.component.ImageEditorDialog
import com.sukisu.ultra.ui.component.KsuIsValid
import com.sukisu.ultra.ui.component.SwitchItem
@@ -103,12 +108,14 @@ import com.sukisu.ultra.ui.util.susfsSUS_SU_Mode
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Locale
import kotlin.math.roundToInt
fun saveCardConfig(context: Context) {
CardConfig.save(context)
}
@SuppressLint("LocalContextConfigurationRead", "ObsoleteSdkInt")
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
@@ -143,6 +150,128 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
stringResource(R.string.theme_dark)
)
// 获取当前语言设置
var currentLanguage by remember {
mutableStateOf(prefs.getString("app_language", "") ?: "")
}
// 获取支持的语言列表
val supportedLanguages = remember {
val languages = mutableListOf<Pair<String, String>>()
languages.add("" to context.getString(R.string.language_follow_system))
val locales = context.resources.configuration.locales
for (i in 0 until locales.size()) {
val locale = locales.get(i)
val code = locale.toLanguageTag()
if (!languages.any { it.first == code }) {
languages.add(code to locale.getDisplayName(locale))
}
}
val commonLocales = listOf(
Locale.forLanguageTag("en"),
Locale.forLanguageTag("zh-CN"),
Locale.forLanguageTag("zh-HK"),
Locale.forLanguageTag("zh-TW"),
Locale.forLanguageTag("ja"), // 日语
Locale.forLanguageTag("fr"), // 法语
Locale.forLanguageTag("de"), // 德语
Locale.forLanguageTag("es"), // 西班牙语
Locale.forLanguageTag("it"), // 意大利语
Locale.forLanguageTag("ru"), // 俄语
Locale.forLanguageTag("pt"), // 葡萄牙语
Locale.forLanguageTag("ko") // 韩语
)
for (locale in commonLocales) {
val code = locale.toLanguageTag()
if (!languages.any { it.first == code }) {
val config = Configuration(context.resources.configuration)
config.setLocale(locale)
try {
val testContext = context.createConfigurationContext(config)
testContext.getString(R.string.language_follow_system)
languages.add(code to locale.getDisplayName(locale))
} catch (_: Exception) {
}
}
}
languages
}
var showLanguageDialog by remember { mutableStateOf(false) }
// 语言切换对话框
if (showLanguageDialog) {
AlertDialog(
onDismissRequest = { showLanguageDialog = false },
title = { Text(stringResource(R.string.language_setting)) },
text = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
supportedLanguages.forEach { (code, name) ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
if (currentLanguage != code) {
prefs.edit {
putString("app_language", code)
commit()
}
currentLanguage = code
Toast.makeText(
context,
context.getString(R.string.language_changed),
Toast.LENGTH_SHORT
).show()
val locale = if (code.isEmpty()) Locale.getDefault() else Locale.forLanguageTag(code)
Locale.setDefault(locale)
val config = Configuration(context.resources.configuration)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context.createConfigurationContext(config)
} else {
@Suppress("DEPRECATION")
context.resources.updateConfiguration(config, context.resources.displayMetrics)
}
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
if (context is Activity) {
context.finish()
}
}
showLanguageDialog = false
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = currentLanguage == code,
onClick = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(name)
}
}
}
},
confirmButton = {
TextButton(
onClick = { showLanguageDialog = false }
) {
Text(stringResource(R.string.cancel))
}
}
)
}
// 简洁模式开关状态
var isSimpleMode by remember {
mutableStateOf(prefs.getBoolean("is_simple_mode", false))
@@ -408,6 +537,35 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
onToggle = { isAppearanceExpanded = !isAppearanceExpanded }
)
AnimatedVisibility(
visible = isAppearanceExpanded,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
ListItem(
headlineContent = { Text(stringResource(R.string.language_setting)) },
supportingContent = {
Text(supportedLanguages.find { it.first == currentLanguage }?.second
?: stringResource(R.string.language_follow_system))
},
leadingContent = {
Icon(
Icons.Default.Language,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
},
trailingContent = {
Icon(
Icons.AutoMirrored.Filled.NavigateNext,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
},
modifier = Modifier.clickable { showLanguageDialog = true }
)
}
AnimatedVisibility(
visible = isAppearanceExpanded,
enter = fadeIn() + expandVertically(),

View File

@@ -342,4 +342,8 @@
<string name="dpi_confirm_message">你确定要将应用DPI从 %1$d 更改为 %2$d 吗?</string>
<string name="dpi_confirm_summary">应用需要重启以应用新的DPI设置不会影响系统状态栏或其他应用</string>
<string name="dpi_applied_success">DPI 已设置为 %1$d重启应用后生效</string>
<!-- 语言设置相关字符串 -->
<string name="language_setting">应用语言</string>
<string name="language_follow_system">跟随系统</string>
<string name="language_changed">语言已更改,重启应用以应用更改</string>
</resources>

View File

@@ -346,4 +346,8 @@
<string name="dpi_confirm_message">Are you sure you want to change the application DPI from %1$d to %2$d?</string>
<string name="dpi_confirm_summary">Application needs to be restarted to apply the new DPI settings, does not affect the system status bar or other applications</string>
<string name="dpi_applied_success">DPI has been set to %1$d, effective after restarting the application</string>
<!-- Language settings related strings -->
<string name="language_setting">App Language</string>
<string name="language_follow_system">Follow System</string>
<string name="language_changed">Language changed, restarting to apply changes</string>
</resources>