From 2da0674c335270c959529f7478e411eabd8163aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 18 Dec 2025 22:25:15 +0800 Subject: [PATCH] Improve per-app proxy --- .../java/io/nekohasekai/sfa/Application.kt | 1 + .../nekohasekai/sfa/bg/AppChangeReceiver.kt | 19 +- .../java/io/nekohasekai/sfa/bg/BoxService.kt | 4 +- .../screen/settings/ProfileOverrideScreen.kt | 335 +++++++++++++----- .../sfa/constant/PerAppProxyUpdateType.kt | 49 --- .../nekohasekai/sfa/constant/SettingsKey.kt | 11 +- .../io/nekohasekai/sfa/database/Settings.kt | 21 +- .../ProfileOverrideActivity.kt | 30 -- .../res/layout/activity_config_override.xml | 18 - app/src/main/res/values-zh-rCN/strings.xml | 4 +- app/src/main/res/values/arrays.xml | 5 - app/src/main/res/values/strings.xml | 28 +- 12 files changed, 322 insertions(+), 203 deletions(-) delete mode 100644 app/src/main/java/io/nekohasekai/sfa/constant/PerAppProxyUpdateType.kt diff --git a/app/src/main/java/io/nekohasekai/sfa/Application.kt b/app/src/main/java/io/nekohasekai/sfa/Application.kt index b3ed09e..d2880f9 100644 --- a/app/src/main/java/io/nekohasekai/sfa/Application.kt +++ b/app/src/main/java/io/nekohasekai/sfa/Application.kt @@ -49,6 +49,7 @@ class Application : Application() { AppChangeReceiver(), IntentFilter().apply { addAction(Intent.ACTION_PACKAGE_ADDED) + addAction(Intent.ACTION_PACKAGE_REPLACED) addDataScheme("package") }, ) diff --git a/app/src/main/java/io/nekohasekai/sfa/bg/AppChangeReceiver.kt b/app/src/main/java/io/nekohasekai/sfa/bg/AppChangeReceiver.kt index 0abf505..871d003 100644 --- a/app/src/main/java/io/nekohasekai/sfa/bg/AppChangeReceiver.kt +++ b/app/src/main/java/io/nekohasekai/sfa/bg/AppChangeReceiver.kt @@ -25,13 +25,8 @@ class AppChangeReceiver : BroadcastReceiver() { Log.d(TAG, "per app proxy disabled") return } - if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { - Log.d(TAG, "skip app update") - return - } - val perAppProxyUpdateOnChange = Settings.perAppProxyUpdateOnChange - if (perAppProxyUpdateOnChange == Settings.PER_APP_PROXY_DISABLED) { - Log.d(TAG, "update on change disabled") + if (!Settings.perAppProxyManagedMode) { + Log.d(TAG, "managed mode disabled") return } val packageName = intent.dataString?.substringAfter("package:") @@ -41,12 +36,12 @@ class AppChangeReceiver : BroadcastReceiver() { } val isChinaApp = PerAppProxyActivity.scanChinaPackage(packageName) Log.d(TAG, "scan china app result for $packageName: $isChinaApp") - if ((perAppProxyUpdateOnChange == Settings.PER_APP_PROXY_INCLUDE) xor !isChinaApp) { - Settings.perAppProxyList += packageName - Log.d(TAG, "added to list") + if (isChinaApp) { + Settings.perAppProxyManagedList += packageName + Log.d(TAG, "added to managed list") } else { - Settings.perAppProxyList -= packageName - Log.d(TAG, "removed from list") + Settings.perAppProxyManagedList -= packageName + Log.d(TAG, "removed from managed list") } } } diff --git a/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt b/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt index 0431e1c..eb1b7f0 100644 --- a/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt +++ b/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt @@ -145,7 +145,7 @@ class BoxService( OverrideOptions().apply { autoRedirect = Settings.autoRedirect if (Vendor.isPerAppProxyAvailable() && Settings.perAppProxyEnabled) { - val appList = Settings.perAppProxyList + val appList = Settings.getEffectivePerAppProxyList() if (Settings.perAppProxyMode == Settings.PER_APP_PROXY_INCLUDE) { includePackage = PlatformInterfaceWrapper.StringArray(appList.iterator()) @@ -228,7 +228,7 @@ class BoxService( OverrideOptions().apply { autoRedirect = Settings.autoRedirect if (Vendor.isPerAppProxyAvailable() && Settings.perAppProxyEnabled) { - val appList = Settings.perAppProxyList + val appList = Settings.getEffectivePerAppProxyList() if (Settings.perAppProxyMode == Settings.PER_APP_PROXY_INCLUDE) { includePackage = PlatformInterfaceWrapper.StringArray(appList.iterator()) } else { diff --git a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/ProfileOverrideScreen.kt b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/ProfileOverrideScreen.kt index bb360ef..eec317d 100644 --- a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/ProfileOverrideScreen.kt +++ b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/ProfileOverrideScreen.kt @@ -1,22 +1,31 @@ package io.nekohasekai.sfa.compose.screen.settings import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowRight +import androidx.compose.material.icons.outlined.AppShortcut import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material.icons.outlined.Route +import androidx.compose.material.icons.outlined.SmartToy import androidx.compose.material3.AlertDialog import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults @@ -37,11 +46,14 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import io.nekohasekai.sfa.Application import io.nekohasekai.sfa.R import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.ui.profileoverride.PerAppProxyActivity import io.nekohasekai.sfa.vendor.Vendor import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -52,6 +64,8 @@ fun ProfileOverrideScreen(navController: NavController) { var autoRedirect by remember { mutableStateOf(Settings.autoRedirect) } var perAppProxyEnabled by remember { mutableStateOf(Settings.perAppProxyEnabled) } + var managedModeEnabled by remember { mutableStateOf(Settings.perAppProxyManagedMode) } + var isScanning by remember { mutableStateOf(false) } var showPerAppProxyDialog by remember { mutableStateOf(false) } Column( @@ -62,6 +76,7 @@ fun ProfileOverrideScreen(navController: NavController) { .verticalScroll(rememberScrollState()) .padding(vertical = 8.dp), ) { + // Card 1: Auto Redirect Card( modifier = Modifier @@ -72,84 +87,102 @@ fun ProfileOverrideScreen(navController: NavController) { containerColor = MaterialTheme.colorScheme.surfaceContainer, ), ) { - Column { - // Auto Redirect - ListItem( - headlineContent = { - Text( - stringResource(R.string.auto_redirect), - style = MaterialTheme.typography.bodyLarge, - ) - }, - supportingContent = { - Text( - stringResource(R.string.auto_redirect_description), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(top = 4.dp), - ) - }, - leadingContent = { - Icon( - imageVector = Icons.Outlined.Route, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - }, - trailingContent = { - Switch( - checked = autoRedirect, - onCheckedChange = { checked -> - if (checked && !autoRedirect) { - scope.launch { - val hasRoot = - withContext(Dispatchers.IO) { - try { - val process = Runtime.getRuntime().exec("su -c id") - process.inputStream.close() - process.outputStream.close() - process.errorStream.close() - process.waitFor() == 0 - } catch (e: Exception) { - false - } + ListItem( + headlineContent = { + Text( + stringResource(R.string.auto_redirect), + style = MaterialTheme.typography.bodyLarge, + ) + }, + supportingContent = { + Text( + stringResource(R.string.auto_redirect_description), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 4.dp), + ) + }, + leadingContent = { + Icon( + imageVector = Icons.Outlined.Route, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + }, + trailingContent = { + Switch( + checked = autoRedirect, + onCheckedChange = { checked -> + if (checked && !autoRedirect) { + scope.launch { + val hasRoot = + withContext(Dispatchers.IO) { + try { + val process = Runtime.getRuntime().exec("su -c id") + process.inputStream.close() + process.outputStream.close() + process.errorStream.close() + process.waitFor() == 0 + } catch (e: Exception) { + false } - if (hasRoot) { - autoRedirect = true - withContext(Dispatchers.IO) { - Settings.autoRedirect = true - } - } else { - Toast.makeText( - context, - context.getString(R.string.root_access_required), - Toast.LENGTH_LONG, - ).show() } - } - } else if (!checked) { - // Disabling doesn't need root check - autoRedirect = false - scope.launch(Dispatchers.IO) { - Settings.autoRedirect = false + if (hasRoot) { + autoRedirect = true + withContext(Dispatchers.IO) { + Settings.autoRedirect = true + } + } else { + Toast.makeText( + context, + context.getString(R.string.root_access_required), + Toast.LENGTH_LONG, + ).show() } } - }, - ) - }, - modifier = Modifier.clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)), - colors = - ListItemDefaults.colors( - containerColor = Color.Transparent, - ), - ) + } else if (!checked) { + autoRedirect = false + scope.launch(Dispatchers.IO) { + Settings.autoRedirect = false + } + } + }, + ) + }, + modifier = Modifier.clip(RoundedCornerShape(12.dp)), + colors = + ListItemDefaults.colors( + containerColor = Color.Transparent, + ), + ) + } - // Per-App Proxy - val isPerAppProxyAvailable = Vendor.isPerAppProxyAvailable() + // Section: Per-App Proxy + val isPerAppProxyAvailable = Vendor.isPerAppProxyAvailable() + + Text( + text = stringResource(R.string.per_app_proxy), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(start = 32.dp, top = 16.dp, bottom = 8.dp), + ) + + Card( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + colors = + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer, + ), + ) { + Column { + // Enabled toggle ListItem( headlineContent = { Text( - stringResource(R.string.per_app_proxy), + stringResource(R.string.enabled), style = MaterialTheme.typography.bodyLarge, ) }, @@ -187,23 +220,132 @@ fun ProfileOverrideScreen(navController: NavController) { } }, modifier = - Modifier - .clip(RoundedCornerShape(bottomStart = 12.dp, bottomEnd = 12.dp)) - .clickable { - if (isPerAppProxyAvailable) { - // Launch the PerAppProxyActivity - val intent = Intent(context, PerAppProxyActivity::class.java) - context.startActivity(intent) - } else { - // Show dialog explaining why it's disabled - showPerAppProxyDialog = true - } + Modifier.clip( + if (perAppProxyEnabled && isPerAppProxyAvailable) { + RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp) + } else { + RoundedCornerShape(12.dp) }, + ), colors = ListItemDefaults.colors( containerColor = Color.Transparent, ), ) + + if (perAppProxyEnabled && isPerAppProxyAvailable) { + // Manage entry + val manageEnabled = !managedModeEnabled + val disabledAlpha = 0.38f + ListItem( + headlineContent = { + Text( + stringResource(R.string.per_app_proxy_manage), + style = MaterialTheme.typography.bodyLarge, + color = if (manageEnabled) { + Color.Unspecified + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = disabledAlpha) + }, + ) + }, + leadingContent = { + Icon( + imageVector = Icons.Outlined.AppShortcut, + contentDescription = null, + tint = if (manageEnabled) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = disabledAlpha) + }, + ) + }, + trailingContent = { + Icon( + imageVector = Icons.AutoMirrored.Outlined.KeyboardArrowRight, + contentDescription = null, + tint = if (manageEnabled) { + MaterialTheme.colorScheme.onSurfaceVariant + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = disabledAlpha) + }, + ) + }, + modifier = + Modifier.clickable(enabled = manageEnabled) { + val intent = Intent(context, PerAppProxyActivity::class.java) + context.startActivity(intent) + }, + colors = + ListItemDefaults.colors( + containerColor = Color.Transparent, + ), + ) + + // Managed Mode toggle + ListItem( + headlineContent = { + Text( + stringResource(R.string.per_app_proxy_managed_mode), + style = MaterialTheme.typography.bodyLarge, + ) + }, + supportingContent = { + Text( + stringResource(R.string.per_app_proxy_managed_mode_description), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 4.dp), + ) + }, + leadingContent = { + Icon( + imageVector = Icons.Outlined.SmartToy, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + }, + trailingContent = { + if (isScanning) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + strokeWidth = 2.dp, + ) + } else { + Switch( + checked = managedModeEnabled, + onCheckedChange = { checked -> + if (checked) { + managedModeEnabled = true + isScanning = true + scope.launch { + withContext(Dispatchers.IO) { + Settings.perAppProxyManagedMode = true + Settings.perAppProxyMode = Settings.PER_APP_PROXY_EXCLUDE + } + val chinaApps = scanAllChinaApps() + withContext(Dispatchers.IO) { + Settings.perAppProxyManagedList = chinaApps + } + isScanning = false + } + } else { + managedModeEnabled = false + scope.launch(Dispatchers.IO) { + Settings.perAppProxyManagedMode = false + } + } + }, + ) + } + }, + modifier = Modifier.clip(RoundedCornerShape(bottomStart = 12.dp, bottomEnd = 12.dp)), + colors = + ListItemDefaults.colors( + containerColor = Color.Transparent, + ), + ) + } } } @@ -228,3 +370,34 @@ fun ProfileOverrideScreen(navController: NavController) { } } } + +private suspend fun scanAllChinaApps(): Set = withContext(Dispatchers.Default) { + val packageManagerFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + PackageManager.MATCH_UNINSTALLED_PACKAGES + } else { + @Suppress("DEPRECATION") + PackageManager.GET_UNINSTALLED_PACKAGES + } + + val installedPackages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Application.packageManager.getInstalledPackages( + PackageManager.PackageInfoFlags.of(packageManagerFlags.toLong()) + ) + } else { + @Suppress("DEPRECATION") + Application.packageManager.getInstalledPackages(packageManagerFlags) + } + + val chinaApps = mutableSetOf() + installedPackages.map { packageInfo -> + async { + if (PerAppProxyActivity.scanChinaPackage(packageInfo.packageName)) { + synchronized(chinaApps) { + chinaApps.add(packageInfo.packageName) + } + } + } + }.awaitAll() + + chinaApps.toSet() +} diff --git a/app/src/main/java/io/nekohasekai/sfa/constant/PerAppProxyUpdateType.kt b/app/src/main/java/io/nekohasekai/sfa/constant/PerAppProxyUpdateType.kt deleted file mode 100644 index 89ba0f7..0000000 --- a/app/src/main/java/io/nekohasekai/sfa/constant/PerAppProxyUpdateType.kt +++ /dev/null @@ -1,49 +0,0 @@ -package io.nekohasekai.sfa.constant - -import android.content.Context -import io.nekohasekai.sfa.R -import io.nekohasekai.sfa.database.Settings - -enum class PerAppProxyUpdateType { - Disabled, - Select, - Deselect, - ; - - fun value() = - when (this) { - Disabled -> Settings.PER_APP_PROXY_DISABLED - Select -> Settings.PER_APP_PROXY_INCLUDE - Deselect -> Settings.PER_APP_PROXY_EXCLUDE - } - - fun getString(context: Context): String { - return when (this) { - Disabled -> context.getString(R.string.disabled) - Select -> context.getString(R.string.per_app_proxy_select) - Deselect -> context.getString(R.string.action_deselect) - } - } - - companion object { - fun valueOf(value: Int): PerAppProxyUpdateType = - when (value) { - Settings.PER_APP_PROXY_DISABLED -> Disabled - Settings.PER_APP_PROXY_INCLUDE -> Select - Settings.PER_APP_PROXY_EXCLUDE -> Deselect - else -> throw IllegalArgumentException() - } - - fun valueOf( - context: Context, - value: String, - ): PerAppProxyUpdateType { - return when (value) { - context.getString(R.string.disabled) -> Disabled - context.getString(R.string.per_app_proxy_select) -> Select - context.getString(R.string.action_deselect) -> Deselect - else -> Disabled - } - } - } -} diff --git a/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt b/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt index 02d110d..9aca0d7 100644 --- a/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt +++ b/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt @@ -4,7 +4,11 @@ object SettingsKey { const val SELECTED_PROFILE = "selected_profile" const val SERVICE_MODE = "service_mode" const val CHECK_UPDATE_ENABLED = "check_update_enabled" + const val UPDATE_CHECK_PROMPTED = "update_check_prompted" const val UPDATE_TRACK = "update_track" + const val SILENT_INSTALL_ENABLED = "silent_install_enabled" + const val SILENT_INSTALL_METHOD = "silent_install_method" + const val AUTO_UPDATE_ENABLED = "auto_update_enabled" const val DISABLE_MEMORY_LIMIT = "disable_memory_limit" const val DYNAMIC_NOTIFICATION = "dynamic_notification" const val USE_COMPOSE_UI = "use_compose_ui" @@ -14,7 +18,8 @@ object SettingsKey { const val PER_APP_PROXY_ENABLED = "per_app_proxy_enabled" const val PER_APP_PROXY_MODE = "per_app_proxy_mode" const val PER_APP_PROXY_LIST = "per_app_proxy_list" - const val PER_APP_PROXY_UPDATE_ON_CHANGE = "per_app_proxy_update_on_change" + const val PER_APP_PROXY_MANAGED_MODE = "per_app_proxy_managed_mode" + const val PER_APP_PROXY_MANAGED_LIST = "per_app_proxy_managed_list" const val SYSTEM_PROXY_ENABLED = "system_proxy_enabled" @@ -23,6 +28,8 @@ object SettingsKey { const val DASHBOARD_DISABLED_ITEMS = "dashboard_disabled_items" // cache - const val STARTED_BY_USER = "started_by_user" + const val CACHED_UPDATE_INFO = "cached_update_info" + const val CACHED_APK_PATH = "cached_apk_path" + const val LAST_SHOWN_UPDATE_VERSION = "last_shown_update_version" } diff --git a/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt b/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt index ba7def4..e3c370c 100644 --- a/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt +++ b/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt @@ -40,7 +40,8 @@ object Settings { var serviceMode by dataStore.string(SettingsKey.SERVICE_MODE) { ServiceMode.NORMAL } var startedByUser by dataStore.boolean(SettingsKey.STARTED_BY_USER) - var checkUpdateEnabled by dataStore.boolean(SettingsKey.CHECK_UPDATE_ENABLED) { true } + var checkUpdateEnabled by dataStore.boolean(SettingsKey.CHECK_UPDATE_ENABLED) { false } + var updateCheckPrompted by dataStore.boolean(SettingsKey.UPDATE_CHECK_PROMPTED) { false } var updateTrack by dataStore.string(SettingsKey.UPDATE_TRACK) { val versionName = BuildConfig.VERSION_NAME.lowercase() if (versionName.contains("-alpha") || @@ -52,6 +53,9 @@ object Settings { "stable" } } + var silentInstallEnabled by dataStore.boolean(SettingsKey.SILENT_INSTALL_ENABLED) { false } + var silentInstallMethod by dataStore.string(SettingsKey.SILENT_INSTALL_METHOD) { "PACKAGE_INSTALLER" } + var autoUpdateEnabled by dataStore.boolean(SettingsKey.AUTO_UPDATE_ENABLED) { false } var disableMemoryLimit by dataStore.boolean(SettingsKey.DISABLE_MEMORY_LIMIT) var dynamicNotification by dataStore.boolean(SettingsKey.DYNAMIC_NOTIFICATION) { true } var useComposeUI by dataStore.boolean(SettingsKey.USE_COMPOSE_UI) { true } @@ -65,13 +69,26 @@ object Settings { var perAppProxyEnabled by dataStore.boolean(SettingsKey.PER_APP_PROXY_ENABLED) { false } var perAppProxyMode by dataStore.int(SettingsKey.PER_APP_PROXY_MODE) { PER_APP_PROXY_EXCLUDE } var perAppProxyList by dataStore.stringSet(SettingsKey.PER_APP_PROXY_LIST) { emptySet() } - var perAppProxyUpdateOnChange by dataStore.int(SettingsKey.PER_APP_PROXY_UPDATE_ON_CHANGE) { PER_APP_PROXY_DISABLED } + var perAppProxyManagedMode by dataStore.boolean(SettingsKey.PER_APP_PROXY_MANAGED_MODE) { false } + var perAppProxyManagedList by dataStore.stringSet(SettingsKey.PER_APP_PROXY_MANAGED_LIST) { emptySet() } + + fun getEffectivePerAppProxyList(): Set { + return if (perAppProxyManagedMode) { + perAppProxyList union perAppProxyManagedList + } else { + perAppProxyList + } + } var systemProxyEnabled by dataStore.boolean(SettingsKey.SYSTEM_PROXY_ENABLED) { true } var dashboardItemOrder by dataStore.string(SettingsKey.DASHBOARD_ITEM_ORDER) { "" } var dashboardDisabledItems by dataStore.stringSet(SettingsKey.DASHBOARD_DISABLED_ITEMS) { emptySet() } + var cachedUpdateInfo by dataStore.string(SettingsKey.CACHED_UPDATE_INFO) { "" } + var cachedApkPath by dataStore.string(SettingsKey.CACHED_APK_PATH) { "" } + var lastShownUpdateVersion by dataStore.int(SettingsKey.LAST_SHOWN_UPDATE_VERSION) { 0 } + fun serviceClass(): Class<*> { return when (serviceMode) { ServiceMode.VPN -> VPNService::class.java diff --git a/app/src/main/java/io/nekohasekai/sfa/ui/profileoverride/ProfileOverrideActivity.kt b/app/src/main/java/io/nekohasekai/sfa/ui/profileoverride/ProfileOverrideActivity.kt index 39bc75e..dc7d43b 100644 --- a/app/src/main/java/io/nekohasekai/sfa/ui/profileoverride/ProfileOverrideActivity.kt +++ b/app/src/main/java/io/nekohasekai/sfa/ui/profileoverride/ProfileOverrideActivity.kt @@ -2,18 +2,10 @@ package io.nekohasekai.sfa.ui.profileoverride import android.content.Intent import android.os.Bundle -import androidx.lifecycle.lifecycleScope import io.nekohasekai.sfa.R -import io.nekohasekai.sfa.constant.PerAppProxyUpdateType import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.databinding.ActivityConfigOverrideBinding -import io.nekohasekai.sfa.ktx.addTextChangedListener -import io.nekohasekai.sfa.ktx.setSimpleItems -import io.nekohasekai.sfa.ktx.text import io.nekohasekai.sfa.ui.shared.AbstractActivity -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class ProfileOverrideActivity : AbstractActivity() { @@ -24,34 +16,12 @@ class ProfileOverrideActivity : binding.switchPerAppProxy.isChecked = Settings.perAppProxyEnabled binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked -> Settings.perAppProxyEnabled = isChecked - binding.perAppProxyUpdateOnChange.isEnabled = binding.switchPerAppProxy.isChecked binding.configureAppListButton.isEnabled = isChecked } - binding.perAppProxyUpdateOnChange.isEnabled = binding.switchPerAppProxy.isChecked binding.configureAppListButton.isEnabled = binding.switchPerAppProxy.isChecked - binding.perAppProxyUpdateOnChange.addTextChangedListener { - lifecycleScope.launch(Dispatchers.IO) { - Settings.perAppProxyUpdateOnChange = - PerAppProxyUpdateType.valueOf(this@ProfileOverrideActivity, it).value() - } - } - binding.configureAppListButton.setOnClickListener { startActivity(Intent(this, PerAppProxyActivity::class.java)) } - lifecycleScope.launch(Dispatchers.IO) { - reloadSettings() - } - } - - private suspend fun reloadSettings() { - val perAppUpdateOnChange = Settings.perAppProxyUpdateOnChange - withContext(Dispatchers.Main) { - binding.perAppProxyUpdateOnChange.text = - PerAppProxyUpdateType.valueOf(perAppUpdateOnChange) - .getString(this@ProfileOverrideActivity) - binding.perAppProxyUpdateOnChange.setSimpleItems(R.array.per_app_proxy_update_on_change_value) - } } } diff --git a/app/src/main/res/layout/activity_config_override.xml b/app/src/main/res/layout/activity_config_override.xml index cabfdd6..33448b0 100644 --- a/app/src/main/res/layout/activity_config_override.xml +++ b/app/src/main/res/layout/activity_config_override.xml @@ -54,24 +54,6 @@ android:layout_marginTop="8dp" android:text="@string/per_app_proxy_description" /> - - - - - - - 找到以下应用程序,请选择您想要的操作。 扫描结果 取消选择 - 新中国应用安装时更新 + 管理 + 托管模式 + 自动排除中国应用 导入配置 您确定要导入配置文件 %s 吗? 当前平台不支持 iCloud 配置文件 diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index dcb5628..bf4fe1b 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -12,9 +12,4 @@ @string/enabled @string/disabled - - @string/disabled - @string/per_app_proxy_select - @string/action_deselect - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1da920a..861af6c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -126,6 +126,8 @@ Per-App Proxy Unavailable Automatic Update Check + Would you like to enable automatic update checking from **Play Store**? + Would you like to enable automatic update checking from **GitHub**? Check Update No updates available New version available: %s @@ -134,6 +136,28 @@ Stable Beta Current track does not support update checking yet + Download & Install + View Release + Downloading… + Download size: %s + Silent Install + Silent Install + Install updates without interaction + Install Method + Select an install method. The permission will be verified immediately after selection. + PackageInstaller + Shizuku + ROOT + Install permission not granted + Grant Install Permission + Allow installing apps from this source + Shizuku is not installed or not running + Shizuku allows apps to use system APIs directly with higher privileges + Get Shizuku + ROOT and Shizuku are not available + %s is not available or permission denied + Auto Update + Automatically download and install updates in background Version %s App version Action @@ -186,7 +210,9 @@ Found the following apps, please choose the action you want. Scan Result Deselect - Update on new China App Installed + Manage + Managed Mode + Automatically Exclude China apps Import profile Are you sure to import profile %s? iCloud profile is not support on current platform