Skip to content

Commit d263550

Browse files
GooolerCopilot
andcommitted
Add Always-on VPN and Ignore Battery Optimizations entries
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent caca04e commit d263550

10 files changed

Lines changed: 196 additions & 0 deletions

File tree

ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/AppSettingsScreen.kt

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,44 @@
11
package com.github.kr328.clash.settings.ui
22

3+
import android.content.Context
4+
import android.content.Intent
5+
import android.net.Uri
6+
import android.net.VpnService
7+
import android.os.PowerManager
8+
import android.provider.Settings
9+
import androidx.activity.compose.rememberLauncherForActivityResult
10+
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
311
import androidx.annotation.StringRes
412
import androidx.compose.foundation.layout.fillMaxSize
513
import androidx.compose.foundation.lazy.LazyColumn
614
import androidx.compose.material3.Icon
715
import androidx.compose.material3.Text
816
import androidx.compose.runtime.Composable
17+
import androidx.compose.runtime.DisposableEffect
918
import androidx.compose.runtime.getValue
19+
import androidx.compose.runtime.mutableStateOf
20+
import androidx.compose.runtime.remember
21+
import androidx.compose.runtime.setValue
1022
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.platform.LocalContext
1124
import androidx.compose.ui.res.stringResource
1225
import androidx.compose.ui.tooling.preview.PreviewWrapper
26+
import androidx.lifecycle.Lifecycle
27+
import androidx.lifecycle.LifecycleEventObserver
28+
import androidx.lifecycle.compose.LocalLifecycleOwner
1329
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1430
import androidx.lifecycle.viewmodel.compose.viewModel
1531
import com.github.kr328.clash.glue.model.DarkMode
1632
import com.github.kr328.clash.settings.R
1733
import com.github.kr328.clash.settings.vm.AppSettingsViewModel
1834
import com.github.kr328.clash.ui.component.TabbyScaffold
35+
import com.github.kr328.clash.ui.icon.BaselineBatterySaver
1936
import com.github.kr328.clash.ui.icon.BaselineBrightness4
2037
import com.github.kr328.clash.ui.icon.BaselineDomain
2138
import com.github.kr328.clash.ui.icon.BaselineHide
2239
import com.github.kr328.clash.ui.icon.BaselineRestore
2340
import com.github.kr328.clash.ui.icon.BaselineStack
41+
import com.github.kr328.clash.ui.icon.BaselineVpnLock
2442
import com.github.kr328.clash.ui.icon.TabbyIcons
2543
import com.github.kr328.clash.ui.theme.PreviewTabby
2644
import com.github.kr328.clash.ui.theme.TabbyThemeWrapper
@@ -34,17 +52,63 @@ internal fun AppSettingsScreen(
3452
modifier: Modifier = Modifier,
3553
viewModel: AppSettingsViewModel = viewModel(),
3654
) {
55+
val context = LocalContext.current
56+
val lifecycleOwner = LocalLifecycleOwner.current
3757
val clashRunning by viewModel.clashRunning.collectAsStateWithLifecycle()
3858
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
59+
var vpnPermissionGranted by remember(context) { mutableStateOf(isVpnPermissionGranted(context)) }
60+
var batteryOptimizationIgnored by
61+
remember(context) { mutableStateOf(isBatteryOptimizationIgnored(context)) }
62+
63+
val vpnPermissionLauncher =
64+
rememberLauncherForActivityResult(StartActivityForResult()) {
65+
vpnPermissionGranted = isVpnPermissionGranted(context)
66+
}
67+
val batteryOptimizationLauncher =
68+
rememberLauncherForActivityResult(StartActivityForResult()) {
69+
batteryOptimizationIgnored = isBatteryOptimizationIgnored(context)
70+
}
71+
72+
DisposableEffect(context, lifecycleOwner) {
73+
val observer = LifecycleEventObserver { _, event ->
74+
if (event == Lifecycle.Event.ON_RESUME) {
75+
vpnPermissionGranted = isVpnPermissionGranted(context)
76+
batteryOptimizationIgnored = isBatteryOptimizationIgnored(context)
77+
}
78+
}
79+
lifecycleOwner.lifecycle.addObserver(observer)
80+
81+
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
82+
}
3983

4084
AppSettingsContent(
4185
clashRunning = clashRunning,
4286
uiState = uiState,
87+
vpnPermissionGranted = vpnPermissionGranted,
88+
batteryOptimizationIgnored = batteryOptimizationIgnored,
4389
onAutoRestartChange = viewModel::updateAutoRestart,
4490
onDarkModeChange = viewModel::updateDarkMode,
4591
onHideAppIconChange = viewModel::updateHideAppIcon,
4692
onHideFromRecentsChange = viewModel::updateHideFromRecents,
4793
onDynamicNotificationChange = viewModel::updateDynamicNotification,
94+
onAlwaysOnVpnChange = { enabled ->
95+
if (!enabled) return@AppSettingsContent
96+
97+
val permissionIntent = VpnService.prepare(context)
98+
if (permissionIntent != null) {
99+
vpnPermissionLauncher.launch(permissionIntent)
100+
} else {
101+
context.startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
102+
}
103+
},
104+
onIgnoreBatteryOptimizationChange = { enabled ->
105+
if (!enabled) return@AppSettingsContent
106+
107+
batteryOptimizationLauncher.launch(
108+
Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
109+
.setData(Uri.parse("package:${context.packageName}"))
110+
)
111+
},
48112
modifier = modifier,
49113
)
50114
}
@@ -53,11 +117,15 @@ internal fun AppSettingsScreen(
53117
private fun AppSettingsContent(
54118
clashRunning: Boolean,
55119
uiState: AppSettingsViewModel.UiState,
120+
vpnPermissionGranted: Boolean,
121+
batteryOptimizationIgnored: Boolean,
56122
onAutoRestartChange: (Boolean) -> Unit,
57123
onDarkModeChange: (DarkMode) -> Unit,
58124
onHideAppIconChange: (Boolean) -> Unit,
59125
onHideFromRecentsChange: (Boolean) -> Unit,
60126
onDynamicNotificationChange: (Boolean) -> Unit,
127+
onAlwaysOnVpnChange: (Boolean) -> Unit,
128+
onIgnoreBatteryOptimizationChange: (Boolean) -> Unit,
61129
modifier: Modifier = Modifier,
62130
) {
63131
TabbyScaffold(title = stringResource(R.string.app), modifier = modifier) { innerPadding ->
@@ -115,6 +183,24 @@ private fun AppSettingsContent(
115183
title = { Text(stringResource(R.string.show_traffic)) },
116184
summary = { Text(stringResource(R.string.show_traffic_summary)) },
117185
)
186+
switchPreference(
187+
key = "always_on_vpn",
188+
value = vpnPermissionGranted,
189+
onValueChange = onAlwaysOnVpnChange,
190+
enabled = !vpnPermissionGranted,
191+
icon = { Icon(imageVector = TabbyIcons.BaselineVpnLock, contentDescription = null) },
192+
title = { Text(stringResource(R.string.always_on_vpn)) },
193+
summary = { Text(stringResource(R.string.always_on_vpn_summary)) },
194+
)
195+
switchPreference(
196+
key = "ignore_battery_optimizations",
197+
value = batteryOptimizationIgnored,
198+
onValueChange = onIgnoreBatteryOptimizationChange,
199+
enabled = !batteryOptimizationIgnored,
200+
icon = { Icon(imageVector = TabbyIcons.BaselineBatterySaver, contentDescription = null) },
201+
title = { Text(stringResource(R.string.ignore_battery_optimizations)) },
202+
summary = { Text(stringResource(R.string.ignore_battery_optimizations_summary)) },
203+
)
118204
}
119205
}
120206
}
@@ -143,11 +229,15 @@ private fun AppSettingsScreenPreview() {
143229
hideFromRecents = false,
144230
dynamicNotification = true,
145231
),
232+
vpnPermissionGranted = false,
233+
batteryOptimizationIgnored = false,
146234
onAutoRestartChange = {},
147235
onDarkModeChange = {},
148236
onHideAppIconChange = {},
149237
onHideFromRecentsChange = {},
150238
onDynamicNotificationChange = {},
239+
onAlwaysOnVpnChange = {},
240+
onIgnoreBatteryOptimizationChange = {},
151241
)
152242
}
153243

@@ -165,10 +255,24 @@ private fun AppSettingsScreenRunningPreview() {
165255
hideFromRecents = true,
166256
dynamicNotification = true,
167257
),
258+
vpnPermissionGranted = true,
259+
batteryOptimizationIgnored = true,
168260
onAutoRestartChange = {},
169261
onDarkModeChange = {},
170262
onHideAppIconChange = {},
171263
onHideFromRecentsChange = {},
172264
onDynamicNotificationChange = {},
265+
onAlwaysOnVpnChange = {},
266+
onIgnoreBatteryOptimizationChange = {},
173267
)
174268
}
269+
270+
private fun isVpnPermissionGranted(context: Context): Boolean {
271+
return VpnService.prepare(context) == null
272+
}
273+
274+
private fun isBatteryOptimizationIgnored(context: Context): Boolean {
275+
return context
276+
.getSystemService(PowerManager::class.java)
277+
?.isIgnoringBatteryOptimizations(context.packageName) == true
278+
}

ui/settings/src/main/res/values-ja-rJP/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<string name="always">Always</string>
1717
<string name="always_dark">常にダークモード</string>
1818
<string name="always_light">常にライトモード</string>
19+
<string name="always_on_vpn">常時 VPN 接続</string>
20+
<string name="always_on_vpn_summary">VPN 権限をリクエストするか VPN 設定を開きます</string>
1921
<string name="app">アプリ</string>
2022
<string name="append_system_dns">システムDNSを追加</string>
2123
<string name="authentication">認証</string>
@@ -80,6 +82,8 @@
8082
<string name="info">情報</string>
8183
<string name="install_time">インストール日時</string>
8284
<string name="interface_">インターフェイス</string>
85+
<string name="ignore_battery_optimizations">電池の最適化を無視</string>
86+
<string name="ignore_battery_optimizations_summary">電池の最適化の対象外にリクエストします</string>
8387
<string name="ipcidr_fallback">IPCIDRフォールバック</string>
8488
<string name="ipv6">IPv6</string>
8589
<string name="key">キー</string>

ui/settings/src/main/res/values-ko-rKR/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<string name="always">강제로 켜기</string>
1717
<string name="always_dark">다크</string>
1818
<string name="always_light">라이트</string>
19+
<string name="always_on_vpn">항상 켜져 있는 VPN</string>
20+
<string name="always_on_vpn_summary">VPN 권한 요청 또는 VPN 설정 열기</string>
1921
<string name="app">앱</string>
2022
<string name="append_system_dns">시스템 DNS 허용</string>
2123
<string name="authentication">인증</string>
@@ -80,6 +82,8 @@
8082
<string name="info">정보</string>
8183
<string name="install_time">설치 시각</string>
8284
<string name="interface_">테마</string>
85+
<string name="ignore_battery_optimizations">배터리 최적화 무시</string>
86+
<string name="ignore_battery_optimizations_summary">배터리 최적화에서 제외 요청</string>
8387
<string name="ipcidr_fallback">IPCIDR 폴백</string>
8488
<string name="ipv6">IPv6</string>
8589
<string name="key">키</string>

ui/settings/src/main/res/values-ru/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<string name="always">Принудительно включить</string>
1717
<string name="always_dark">Всегда тёмная</string>
1818
<string name="always_light">Всегда светлая</string>
19+
<string name="always_on_vpn">Постоянное VPN-соединение</string>
20+
<string name="always_on_vpn_summary">Запросить разрешение VPN или открыть настройки VPN</string>
1921
<string name="app">Приложение</string>
2022
<string name="append_system_dns">Добавить системный DNS</string>
2123
<string name="authentication">Аутентификация</string>
@@ -80,6 +82,8 @@
8082
<string name="info">Инфо</string>
8183
<string name="install_time">Время установки</string>
8284
<string name="interface_">Интерфейс</string>
85+
<string name="ignore_battery_optimizations">Игнорировать оптимизацию батареи</string>
86+
<string name="ignore_battery_optimizations_summary">Запросить исключение из оптимизации батареи</string>
8387
<string name="ipcidr_fallback">Резервный IPCIDR</string>
8488
<string name="ipv6">IPv6</string>
8589
<string name="key">Ключ</string>

ui/settings/src/main/res/values-vi/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<string name="always">Luôn luôn</string>
1717
<string name="always_dark">Luôn tối</string>
1818
<string name="always_light">Luôn sáng</string>
19+
<string name="always_on_vpn">VPN luôn bật</string>
20+
<string name="always_on_vpn_summary">Yêu cầu quyền VPN hoặc mở cài đặt VPN</string>
1921
<string name="app">Ứng dụng</string>
2022
<string name="append_system_dns">Nối hệ thống DNS</string>
2123
<string name="authentication">Xác thực</string>
@@ -80,6 +82,8 @@
8082
<string name="info">Thông tin</string>
8183
<string name="install_time">Thời gian cài đặt</string>
8284
<string name="interface_">Giao diện</string>
85+
<string name="ignore_battery_optimizations">Bỏ qua tối ưu hóa pin</string>
86+
<string name="ignore_battery_optimizations_summary">Yêu cầu loại trừ khỏi tối ưu hóa pin</string>
8387
<string name="ipcidr_fallback">Dự phòng IPCIDR</string>
8488
<string name="ipv6">IPv6</string>
8589
<string name="key">Khoá</string>

ui/settings/src/main/res/values-zh-rHK/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<string name="always">強制開啟</string>
1717
<string name="always_dark">總是暗黑模式</string>
1818
<string name="always_light">總是明亮模式</string>
19+
<string name="always_on_vpn">始終開啟 VPN</string>
20+
<string name="always_on_vpn_summary">請求 VPN 權限或打開 VPN 設置</string>
1921
<string name="app">應用</string>
2022
<string name="append_system_dns">追加系統 DNS</string>
2123
<string name="authentication">認證</string>
@@ -80,6 +82,8 @@
8082
<string name="info">消息</string>
8183
<string name="install_time">安裝時間</string>
8284
<string name="interface_">界面</string>
85+
<string name="ignore_battery_optimizations">忽略電池優化</string>
86+
<string name="ignore_battery_optimizations_summary">請求將此應用排除在電池優化之外</string>
8387
<string name="ipcidr_fallback">IPCIDR Fallback</string>
8488
<string name="ipv6">IPv6</string>
8589
<string name="key">鍵</string>

ui/settings/src/main/res/values-zh-rTW/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<string name="always">強制開啟</string>
1717
<string name="always_dark">深色</string>
1818
<string name="always_light">淺色</string>
19+
<string name="always_on_vpn">永遠開啟 VPN</string>
20+
<string name="always_on_vpn_summary">請求 VPN 權限或開啟 VPN 設定</string>
1921
<string name="app">應用</string>
2022
<string name="append_system_dns">附加作業系統 DNS</string>
2123
<string name="authentication">認證</string>
@@ -80,6 +82,8 @@
8082
<string name="info">資訊</string>
8183
<string name="install_time">安裝時間</string>
8284
<string name="interface_">介面</string>
85+
<string name="ignore_battery_optimizations">忽略電池最佳化</string>
86+
<string name="ignore_battery_optimizations_summary">請求將此應用程式排除於電池最佳化之外</string>
8387
<string name="ipcidr_fallback">IPCIDR 後饋</string>
8488
<string name="ipv6">IPv6</string>
8589
<string name="key">鍵</string>

ui/settings/src/main/res/values-zh/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<string name="always">强制开启</string>
1717
<string name="always_dark">总是暗黑模式</string>
1818
<string name="always_light">总是明亮模式</string>
19+
<string name="always_on_vpn">始终开启 VPN</string>
20+
<string name="always_on_vpn_summary">请求 VPN 权限或打开 VPN 设置</string>
1921
<string name="app">应用</string>
2022
<string name="append_system_dns">追加系统 DNS</string>
2123
<string name="authentication">认证</string>
@@ -80,6 +82,8 @@
8082
<string name="info">消息</string>
8183
<string name="install_time">安装时间</string>
8284
<string name="interface_">界面</string>
85+
<string name="ignore_battery_optimizations">忽略电池优化</string>
86+
<string name="ignore_battery_optimizations_summary">请求将此应用排除在电池优化之外</string>
8387
<string name="ipcidr_fallback">IPCIDR Fallback</string>
8488
<string name="ipv6">IPv6</string>
8589
<string name="key">键</string>

ui/settings/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
<string name="always">Always</string>
1717
<string name="always_dark">Always Dark</string>
1818
<string name="always_light">Always Light</string>
19+
<string name="always_on_vpn">Always-on VPN</string>
20+
<string name="always_on_vpn_summary">Request VPN permission or open VPN settings</string>
1921
<string name="app">App</string>
2022
<string name="append_system_dns">Append System DNS</string>
2123
<string name="authentication">Authentication</string>
@@ -78,6 +80,8 @@
7880
<string name="import_geoip_file">Import GeoIP Database</string>
7981
<string name="import_geosite_file">Import GeoSite Database</string>
8082
<string name="info">Info</string>
83+
<string name="ignore_battery_optimizations">Ignore Battery Optimizations</string>
84+
<string name="ignore_battery_optimizations_summary">Request battery optimization exclusion</string>
8185
<string name="install_time">Install Time</string>
8286
<string name="interface_">Interface</string>
8387
<string name="ipcidr_fallback">IPCIDR Fallback</string>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.github.kr328.clash.ui.icon
2+
3+
import androidx.compose.ui.graphics.Color
4+
import androidx.compose.ui.graphics.SolidColor
5+
import androidx.compose.ui.graphics.vector.ImageVector
6+
import androidx.compose.ui.graphics.vector.path
7+
import androidx.compose.ui.unit.dp
8+
9+
@Suppress("UnusedReceiverParameter")
10+
val TabbyIcons.BaselineBatterySaver: ImageVector
11+
get() {
12+
if (_BaselineBatterySaver != null) {
13+
return _BaselineBatterySaver!!
14+
}
15+
_BaselineBatterySaver =
16+
ImageVector.Builder(
17+
name = "BaselineBatterySaver",
18+
defaultWidth = 24.dp,
19+
defaultHeight = 24.dp,
20+
viewportWidth = 24f,
21+
viewportHeight = 24f,
22+
)
23+
.apply {
24+
path(fill = SolidColor(Color.White)) {
25+
moveTo(17f, 4f)
26+
horizontalLineToRelative(-3f)
27+
verticalLineTo(2f)
28+
horizontalLineToRelative(-4f)
29+
verticalLineToRelative(2f)
30+
horizontalLineTo(7f)
31+
curveToRelative(-1.1f, 0f, -2f, 0.9f, -2f, 2f)
32+
verticalLineToRelative(15f)
33+
curveToRelative(0f, 1.1f, 0.9f, 2f, 2f, 2f)
34+
horizontalLineToRelative(10f)
35+
curveToRelative(1.1f, 0f, 2f, -0.9f, 2f, -2f)
36+
verticalLineTo(6f)
37+
curveToRelative(0f, -1.1f, -0.9f, -2f, -2f, -2f)
38+
close()
39+
moveTo(16f, 15f)
40+
horizontalLineToRelative(-2f)
41+
verticalLineToRelative(2f)
42+
horizontalLineToRelative(-2f)
43+
verticalLineToRelative(-2f)
44+
horizontalLineToRelative(-2f)
45+
verticalLineToRelative(-2f)
46+
horizontalLineToRelative(2f)
47+
verticalLineToRelative(-2f)
48+
horizontalLineToRelative(2f)
49+
verticalLineToRelative(2f)
50+
horizontalLineToRelative(2f)
51+
verticalLineToRelative(2f)
52+
close()
53+
}
54+
}
55+
.build()
56+
57+
return _BaselineBatterySaver!!
58+
}
59+
60+
@Suppress("ObjectPropertyName") private var _BaselineBatterySaver: ImageVector? = null

0 commit comments

Comments
 (0)