Add language options
- Fix some icon color issues
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -60,7 +60,7 @@ fun SwitchItem(
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
maxLines = 1,
|
||||
maxLines = Int.MAX_VALUE,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user