Skip to content

Commit 0e2489e

Browse files
committed
feat: add in-app language switching and improve VPN startup
1 parent bbba04b commit 0e2489e

11 files changed

Lines changed: 117 additions & 5 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@
2020
android:label="@string/app_name"
2121
android:roundIcon="@mipmap/ic_launcher"
2222
android:supportsRtl="true"
23-
android:theme="@style/Theme.MikuBoxCla">
23+
android:theme="@style/Theme.MikuBoxCla"
24+
android:localeConfig="@xml/locales_config">
25+
26+
<meta-data
27+
android:name="autoStoreLocales"
28+
android:value="true" />
2429

2530
<activity
2631
android:name=".ui.SplashActivity"

app/src/main/java/top/uwu/mikubox/MikuApp.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ package top.uwu.mikubox
33
import android.app.Application
44
import androidx.appcompat.app.AppCompatDelegate
55
import top.uwu.mikubox.core.AppSettings
6+
import androidx.core.os.LocaleListCompat
67

78
/** Applies the persisted theme before any activity is created. */
89
class MikuApp : Application() {
910
override fun onCreate() {
1011
super.onCreate()
1112
AppCompatDelegate.setDefaultNightMode(AppSettings.nightMode(this))
13+
val language = AppSettings.language(this)
14+
if (AppCompatDelegate.applicationLocales.toLanguageTags() != language) {
15+
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(language))
16+
}
1217
}
1318
}

app/src/main/java/top/uwu/mikubox/core/AppSettings.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,21 @@ object AppSettings {
99
private const val PREFS = "miku_app_settings"
1010
private const val KEY_NIGHT_MODE = "night_mode"
1111
private const val KEY_PARTICLES = "particles"
12+
private const val KEY_LANGUAGE = "language"
13+
14+
const val LANGUAGE_SYSTEM = ""
15+
const val LANGUAGE_ENGLISH = "en"
16+
const val LANGUAGE_TRADITIONAL_CHINESE = "zh-TW"
17+
const val LANGUAGE_SIMPLIFIED_CHINESE = "zh-CN"
18+
const val LANGUAGE_FRENCH = "fr"
19+
const val LANGUAGE_INDONESIAN = "in"
20+
const val LANGUAGE_RUSSIAN = "ru"
21+
22+
val supportedLanguages = listOf(
23+
LANGUAGE_SYSTEM, LANGUAGE_ENGLISH, LANGUAGE_TRADITIONAL_CHINESE,
24+
LANGUAGE_SIMPLIFIED_CHINESE, LANGUAGE_FRENCH, LANGUAGE_INDONESIAN,
25+
LANGUAGE_RUSSIAN,
26+
)
1227

1328
/** One of [AppCompatDelegate].MODE_NIGHT_FOLLOW_SYSTEM / _NO / _YES. */
1429
fun nightMode(context: Context): Int =
@@ -26,5 +41,12 @@ object AppSettings {
2641
prefs(context).edit().putBoolean(KEY_PARTICLES, enabled).commit()
2742
}
2843

44+
fun language(context: Context): String =
45+
prefs(context).getString(KEY_LANGUAGE, LANGUAGE_SYSTEM).orEmpty()
46+
47+
fun setLanguage(context: Context, languageTag: String) {
48+
prefs(context).edit().putString(KEY_LANGUAGE, languageTag).commit()
49+
}
50+
2951
private fun prefs(context: Context) = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
3052
}

app/src/main/java/top/uwu/mikubox/service/MihomoVpnSettings.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ object MihomoVpnSettings {
1212
private const val APP_MODE = "app_mode"
1313
private const val PACKAGES = "packages"
1414

15-
fun mtu(context: Context): Int = prefs(context).getInt(MTU, 9000).coerceIn(1280, 65_535)
15+
// 1500 is supported by every Android VPN implementation. Jumbo frames
16+
// can make Builder.establish() fail on devices that do not support them.
17+
fun mtu(context: Context): Int = prefs(context).getInt(MTU, 1500).coerceIn(1280, 9_000)
1618

1719
fun setMtu(context: Context, value: Int) {
18-
prefs(context).edit().putInt(MTU, value.coerceIn(1280, 65_535)).commit()
20+
prefs(context).edit().putInt(MTU, value.coerceIn(1280, 9_000)).commit()
1921
}
2022

2123
fun appMode(context: Context): AppMode = runCatching {

app/src/main/java/top/uwu/mikubox/service/MikuVpnService.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import top.uwu.mikubox.ui.MainActivity
3232
class MikuVpnService : VpnService() {
3333

3434
private var tun: ParcelFileDescriptor? = null
35+
private var lastTunError: Throwable? = null
3536

3637
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
3738
if (intent?.action == ACTION_STOP) {
@@ -55,11 +56,12 @@ class MikuVpnService : VpnService() {
5556

5657
private fun startVpn() {
5758
if (tun != null) return
59+
lastTunError = null
5860
try {
5961
MikuProxyService.stop(this)
6062
startForegroundNotification()
6163
val descriptor = establishTun() ?: run {
62-
reportStartFailure(getString(R.string.mihomo_start_failed))
64+
reportStartFailure(lastTunError?.message.orEmpty())
6365
stopVpn()
6466
return
6567
}
@@ -134,7 +136,7 @@ class MikuVpnService : VpnService() {
134136
return try {
135137
builder.establish()
136138
} catch (t: Throwable) {
137-
stopVpn()
139+
lastTunError = t
138140
null
139141
}
140142
}

app/src/main/java/top/uwu/mikubox/ui/SettingsActivity.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.os.Bundle
44
import android.text.InputType
55
import android.widget.EditText
66
import androidx.appcompat.app.AppCompatDelegate
7+
import androidx.core.os.LocaleListCompat
78
import com.google.android.material.dialog.MaterialAlertDialogBuilder
89
import top.uwu.mikubox.R
910
import top.uwu.mikubox.core.AppSettings
@@ -44,6 +45,7 @@ class SettingsActivity : EdgeToEdgeActivity() {
4445
binding.toolbar.setNavigationOnClickListener { finish() }
4546

4647
binding.rowTheme.setOnClickListener { pickTheme() }
48+
binding.rowLanguage.setOnClickListener { pickLanguage() }
4749
binding.rowMtu.setOnClickListener { editMtu() }
4850
binding.rowDns.setOnClickListener {
4951
startActivity(Intent(this, DnsActivity::class.java))
@@ -113,6 +115,7 @@ class SettingsActivity : EdgeToEdgeActivity() {
113115

114116
private fun render() {
115117
binding.tvThemeValue.text = getString(themeLabel(AppSettings.nightMode(this)))
118+
binding.tvLanguageValue.text = getString(languageLabel(AppSettings.language(this)))
116119
binding.tvMtuValue.text = MihomoVpnSettings.mtu(this).toString()
117120
binding.tvAppsValue.setText(appModeLabel(MihomoVpnSettings.appMode(this)))
118121
binding.tvModeValue.setText(modeLabel(MihomoCoreSettings.mode(this)))
@@ -246,6 +249,31 @@ class SettingsActivity : EdgeToEdgeActivity() {
246249
.show()
247250
}
248251

252+
private fun languageLabel(language: String): Int = when (language) {
253+
AppSettings.LANGUAGE_ENGLISH -> R.string.language_english
254+
AppSettings.LANGUAGE_TRADITIONAL_CHINESE -> R.string.language_traditional_chinese
255+
AppSettings.LANGUAGE_SIMPLIFIED_CHINESE -> R.string.language_simplified_chinese
256+
AppSettings.LANGUAGE_FRENCH -> R.string.language_french
257+
AppSettings.LANGUAGE_INDONESIAN -> R.string.language_indonesian
258+
AppSettings.LANGUAGE_RUSSIAN -> R.string.language_russian
259+
else -> R.string.language_system
260+
}
261+
262+
private fun pickLanguage() {
263+
val languages = AppSettings.supportedLanguages
264+
val labels = languages.map { getString(languageLabel(it)) }.toTypedArray()
265+
MaterialAlertDialogBuilder(this)
266+
.setTitle(R.string.settings_language)
267+
.setSingleChoiceItems(labels, languages.indexOf(AppSettings.language(this))) { dialog, which ->
268+
val language = languages[which]
269+
AppSettings.setLanguage(this, language)
270+
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(language))
271+
dialog.dismiss()
272+
}
273+
.setNegativeButton(android.R.string.cancel, null)
274+
.show()
275+
}
276+
249277
private fun writeBackup(uri: Uri) {
250278
val ok = runCatching {
251279
contentResolver.openOutputStream(uri)?.use {

app/src/main/res/layout/activity_settings.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@
104104

105105
<View style="@style/SettingsDivider" />
106106

107+
<LinearLayout
108+
android:id="@+id/row_language"
109+
style="@style/SettingsRow">
110+
<LinearLayout style="@style/SettingsRowText">
111+
<TextView
112+
style="@style/SettingsRowTitle"
113+
android:text="@string/settings_language" />
114+
<TextView
115+
android:id="@+id/tv_language_value"
116+
style="@style/SettingsRowSummary" />
117+
</LinearLayout>
118+
</LinearLayout>
119+
120+
<View style="@style/SettingsDivider" />
121+
107122
<LinearLayout
108123
android:id="@+id/row_particles"
109124
style="@style/SettingsRow">

app/src/main/res/values-zh-rCN/strings.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@
5555
<string name="tools_and_info">工具与信息</string>
5656
<string name="menu_home">主页</string>
5757
<string name="settings">设置</string>
58+
<string name="settings_language">语言</string>
59+
<string name="language_system">系统默认</string>
60+
<string name="language_english">English</string>
61+
<string name="language_traditional_chinese">繁體中文</string>
62+
<string name="language_simplified_chinese">简体中文</string>
63+
<string name="language_french">Français</string>
64+
<string name="language_indonesian">Bahasa Indonesia</string>
65+
<string name="language_russian">Русский</string>
5866
<string name="menu_log">Logcat</string>
5967
<string name="desc_menu_log">查看应用日志</string>
6068
<string name="menu_tools">工具</string>

app/src/main/res/values-zh-rTW/strings.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@
5555
<string name="tools_and_info">工具與資訊</string>
5656
<string name="menu_home">首頁</string>
5757
<string name="settings">設定</string>
58+
<string name="settings_language">語言</string>
59+
<string name="language_system">系統預設</string>
60+
<string name="language_english">English</string>
61+
<string name="language_traditional_chinese">繁體中文</string>
62+
<string name="language_simplified_chinese">简体中文</string>
63+
<string name="language_french">Français</string>
64+
<string name="language_indonesian">Bahasa Indonesia</string>
65+
<string name="language_russian">Русский</string>
5866
<string name="menu_log">Logcat</string>
5967
<string name="desc_menu_log">檢視應用程式紀錄</string>
6068
<string name="menu_tools">工具</string>

app/src/main/res/values/strings.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@
6969
<string name="tools_and_info">Tools &amp; info</string>
7070
<string name="menu_home">Home</string>
7171
<string name="settings">Settings</string>
72+
<string name="settings_language">Language</string>
73+
<string name="language_system">System default</string>
74+
<string name="language_english">English</string>
75+
<string name="language_traditional_chinese">繁體中文</string>
76+
<string name="language_simplified_chinese">简体中文</string>
77+
<string name="language_french">Français</string>
78+
<string name="language_indonesian">Bahasa Indonesia</string>
79+
<string name="language_russian">Русский</string>
7280
<string name="menu_log">Logcat</string>
7381
<string name="desc_menu_log">View app logs</string>
7482
<string name="menu_tools">Tools</string>

0 commit comments

Comments
 (0)