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 package com.sukisu.ultra
import android.annotation.SuppressLint
import android.app.Application 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.Coil
import coil.ImageLoader import coil.ImageLoader
import com.dergoogler.mmrl.platform.Platform import com.dergoogler.mmrl.platform.Platform
import me.zhanghai.android.appiconloader.coil.AppIconFetcher import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import java.io.File import java.io.File
import java.util.Locale
lateinit var ksuApp: KernelSUApplication lateinit var ksuApp: KernelSUApplication
class KernelSUApplication : Application() { 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() { override fun onCreate() {
super.onCreate() super.onCreate()
ksuApp = this 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 package com.sukisu.ultra.ui
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.Configuration
import android.database.ContentObserver import android.database.ContentObserver
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -36,6 +38,7 @@ import com.sukisu.ultra.ui.util.*
import androidx.core.content.edit import androidx.core.content.edit
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
import com.sukisu.ultra.ui.webui.initPlatform import com.sukisu.ultra.ui.webui.initPlatform
import java.util.Locale
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private inner class ThemeChangeContentObserver( 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?) { override fun onCreate(savedInstanceState: Bundle?) {
// 应用DPI设置仅对当前应用生效 // 确保应用正确的语言设置
applyLanguageSetting()
applyCustomDpi() applyCustomDpi()
// Enable edge to edge // Enable edge to edge
@@ -138,7 +182,7 @@ class MainActivity : ComponentActivity() {
} }
} }
// 应用自定义DPI设置(仅对当前应用生效) // 应用自定义DPI设置
private fun applyCustomDpi() { private fun applyCustomDpi() {
val prefs = getSharedPreferences("settings", MODE_PRIVATE) val prefs = getSharedPreferences("settings", MODE_PRIVATE)
val customDpi = prefs.getInt("app_dpi", 0) val customDpi = prefs.getInt("app_dpi", 0)
@@ -147,9 +191,8 @@ class MainActivity : ComponentActivity() {
try { try {
val resources = resources val resources = resources
val metrics = resources.displayMetrics val metrics = resources.displayMetrics
// 仅更新应用内显示,不影响系统状态栏
metrics.density = customDpi / 160f metrics.density = customDpi / 160f
@Suppress("DEPRECATION")
metrics.scaledDensity = customDpi / 160f metrics.scaledDensity = customDpi / 160f
metrics.densityDpi = customDpi metrics.densityDpi = customDpi
} catch (e: Exception) { } catch (e: Exception) {
@@ -169,6 +212,8 @@ class MainActivity : ComponentActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
applyLanguageSetting()
if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) { if (!ThemeConfig.backgroundImageLoaded && !ThemeConfig.preventBackgroundRefresh) {
loadCustomBackground() loadCustomBackground()
} }
@@ -180,6 +225,11 @@ class MainActivity : ComponentActivity() {
destroyListeners.forEach { it() } destroyListeners.forEach { it() }
super.onDestroy() super.onDestroy()
} }
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
applyLanguageSetting()
}
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)

View File

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

View File

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

View File

@@ -1,7 +1,10 @@
package com.sukisu.ultra.ui.screen package com.sukisu.ultra.ui.screen
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.widget.Toast 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.DarkMode
import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp 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.Opacity
import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.Security 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.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.sukisu.ultra.ui.MainActivity
import com.sukisu.ultra.ui.component.ImageEditorDialog import com.sukisu.ultra.ui.component.ImageEditorDialog
import com.sukisu.ultra.ui.component.KsuIsValid import com.sukisu.ultra.ui.component.KsuIsValid
import com.sukisu.ultra.ui.component.SwitchItem 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 com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Locale
import kotlin.math.roundToInt import kotlin.math.roundToInt
fun saveCardConfig(context: Context) { fun saveCardConfig(context: Context) {
CardConfig.save(context) CardConfig.save(context)
} }
@SuppressLint("LocalContextConfigurationRead", "ObsoleteSdkInt")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
@@ -143,6 +150,128 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
stringResource(R.string.theme_dark) 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 { var isSimpleMode by remember {
mutableStateOf(prefs.getBoolean("is_simple_mode", false)) mutableStateOf(prefs.getBoolean("is_simple_mode", false))
@@ -408,6 +537,35 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
onToggle = { isAppearanceExpanded = !isAppearanceExpanded } 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( AnimatedVisibility(
visible = isAppearanceExpanded, visible = isAppearanceExpanded,
enter = fadeIn() + expandVertically(), enter = fadeIn() + expandVertically(),

View File

@@ -342,4 +342,8 @@
<string name="dpi_confirm_message">你确定要将应用DPI从 %1$d 更改为 %2$d 吗?</string> <string name="dpi_confirm_message">你确定要将应用DPI从 %1$d 更改为 %2$d 吗?</string>
<string name="dpi_confirm_summary">应用需要重启以应用新的DPI设置不会影响系统状态栏或其他应用</string> <string name="dpi_confirm_summary">应用需要重启以应用新的DPI设置不会影响系统状态栏或其他应用</string>
<string name="dpi_applied_success">DPI 已设置为 %1$d重启应用后生效</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> </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_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_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> <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> </resources>