From 4812f067dbd9cfd811f5af99d31753cb400312e3 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Tue, 30 Dec 2025 16:16:32 +0800 Subject: [PATCH] Add policy group config type and UI support Introduces a new POLICYGROUP config type for server grouping and selection. Adds ServerGroupActivity and related UI, updates menu options, and removes legacy intelligent selection logic. Refactors config management and filtering to support policy groups, and updates translations and resources accordingly. --- V2rayNG/app/src/main/AndroidManifest.xml | 4 + .../src/main/java/com/v2ray/ang/AppConfig.kt | 1 - .../java/com/v2ray/ang/dto/EConfigType.kt | 3 +- .../java/com/v2ray/ang/dto/ProfileItem.kt | 4 + .../com/v2ray/ang/dto/SubscriptionItem.kt | 1 - .../com/v2ray/ang/handler/AngConfigManager.kt | 28 +-- .../v2ray/ang/handler/V2RayServiceManager.kt | 3 +- .../v2ray/ang/handler/V2rayConfigManager.kt | 110 ++++++------ .../java/com/v2ray/ang/ui/MainActivity.kt | 33 ++-- .../com/v2ray/ang/ui/MainRecyclerAdapter.kt | 24 ++- .../java/com/v2ray/ang/ui/ServerActivity.kt | 3 +- .../ang/ui/ServerCustomConfigActivity.kt | 2 +- .../com/v2ray/ang/ui/ServerGroupActivity.kt | 165 ++++++++++++++++++ .../java/com/v2ray/ang/ui/SettingsActivity.kt | 1 - .../java/com/v2ray/ang/ui/SubEditActivity.kt | 3 - .../com/v2ray/ang/viewmodel/MainViewModel.kt | 23 --- .../v2ray/ang/viewmodel/SettingsViewModel.kt | 1 - .../layout/activity_server_custom_config.xml | 11 +- .../main/res/layout/activity_server_group.xml | 106 +++++++++++ .../src/main/res/layout/activity_sub_edit.xml | 19 -- V2rayNG/app/src/main/res/menu/menu_main.xml | 8 +- .../app/src/main/res/values-ar/strings.xml | 11 +- .../app/src/main/res/values-bn/strings.xml | 11 +- .../src/main/res/values-bqi-rIR/strings.xml | 11 +- .../app/src/main/res/values-fa/strings.xml | 11 +- .../app/src/main/res/values-ru/strings.xml | 11 +- .../app/src/main/res/values-vi/strings.xml | 11 +- .../src/main/res/values-zh-rCN/strings.xml | 27 +-- .../src/main/res/values-zh-rTW/strings.xml | 27 +-- V2rayNG/app/src/main/res/values/arrays.xml | 5 - V2rayNG/app/src/main/res/values/strings.xml | 35 ++-- .../app/src/main/res/xml/pref_settings.xml | 8 - 32 files changed, 473 insertions(+), 248 deletions(-) create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerGroupActivity.kt create mode 100644 V2rayNG/app/src/main/res/layout/activity_server_group.xml diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index 76890065..f25602a9 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -79,6 +79,10 @@ android:name=".ui.ServerCustomConfigActivity" android:exported="false" android:windowSoftInputMode="stateUnchanged" /> + diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt index 54e780ed..1099e6c2 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt @@ -58,7 +58,6 @@ object AppConfig { const val PREF_DELAY_TEST_URL = "pref_delay_test_url" const val PREF_LOGLEVEL = "pref_core_loglevel" const val PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD = "pref_outbound_domain_resolve_method" - const val PREF_INTELLIGENT_SELECTION_METHOD = "pref_intelligent_selection_method" const val PREF_MODE = "pref_mode" const val PREF_IS_BOOTED = "pref_is_booted" const val PREF_CHECK_UPDATE_PRE_RELEASE = "pref_check_update_pre_release" diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/EConfigType.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/EConfigType.kt index ed7cad3b..cbc5ee21 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/EConfigType.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/EConfigType.kt @@ -14,7 +14,8 @@ enum class EConfigType(val value: Int, val protocolScheme: String) { // TUIC(8, AppConfig.TUIC), HYSTERIA2(9, AppConfig.HYSTERIA2), - HTTP(10, AppConfig.HTTP); + HTTP(10, AppConfig.HTTP), + POLICYGROUP (101, AppConfig.CUSTOM); companion object { fun fromInt(value: Int) = entries.firstOrNull { it.value == value } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileItem.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileItem.kt index 16e4620b..e035f687 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileItem.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileItem.kt @@ -59,6 +59,10 @@ data class ProfileItem( var bandwidthDown: String? = null, var bandwidthUp: String? = null, + var policyGroupType: String? = null, + var policyGroupSubscriptionId: String? = null, + var policyGroupFilter: String? = null, + ) { companion object { fun create(configType: EConfigType): ProfileItem { diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/SubscriptionItem.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/SubscriptionItem.kt index beac5147..8ab871c3 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/SubscriptionItem.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/SubscriptionItem.kt @@ -11,7 +11,6 @@ data class SubscriptionItem( var prevProfile: String? = null, var nextProfile: String? = null, var filter: String? = null, - var intelligentSelectionFilter: String? = null, var allowInsecureUrl: Boolean = false, var userAgent: String? = null, ) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt index 779946a4..1108cab5 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt @@ -148,6 +148,7 @@ object AngConfigManager { EConfigType.TROJAN -> TrojanFmt.toUri(config) EConfigType.WIREGUARD -> WireguardFmt.toUri(config) EConfigType.HYSTERIA2 -> Hysteria2Fmt.toUri(config) + EConfigType.POLICYGROUP -> "" } } catch (e: Exception) { Log.e(AppConfig.TAG, "Failed to share config for GUID: $guid", e) @@ -491,31 +492,4 @@ object AngConfigManager { MmkvManager.encodeSubscription("", subItem) return 1 } - - /** - * Creates an intelligent selection configuration based on multiple server configurations. - * - * @param context The application context used for configuration generation. - * @param guidList The list of server GUIDs to be included in the intelligent selection. - * Each GUID represents a server configuration that will be combined. - * @param subid The subscription ID to associate with the generated configuration. - * This helps organize the configuration under a specific subscription. - * @return The GUID key of the newly created intelligent selection configuration, - * or null if the operation fails (e.g., empty guidList or configuration parsing error). - */ - fun createIntelligentSelection( - context: Context, - guidList: List, - subid: String - ): String? { - if (guidList.isEmpty()) { - return null - } - val result = V2rayConfigManager.genV2rayConfig(context, guidList) ?: return null - val config = CustomFmt.parse(JsonUtil.toJson(result)) ?: return null - config.subscriptionId = subid - val key = MmkvManager.encodeServerConfig("", config) - MmkvManager.encodeServerRaw(key, JsonUtil.toJsonPretty(result) ?: "") - return key - } } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2RayServiceManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2RayServiceManager.kt index edd03898..e6d1f983 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2RayServiceManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2RayServiceManager.kt @@ -5,7 +5,6 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.os.Build import android.util.Log import androidx.core.content.ContextCompat import com.v2ray.ang.AppConfig @@ -17,7 +16,6 @@ import com.v2ray.ang.service.ServiceControl import com.v2ray.ang.service.V2RayProxyOnlyService import com.v2ray.ang.service.V2RayVpnService import com.v2ray.ang.util.MessageUtil -import com.v2ray.ang.handler.PluginServiceManager import com.v2ray.ang.util.Utils import go.Seq import kotlinx.coroutines.CoroutineScope @@ -100,6 +98,7 @@ object V2RayServiceManager { val guid = MmkvManager.getSelectServer() ?: return val config = MmkvManager.decodeServerConfig(guid) ?: return if (config.configType != EConfigType.CUSTOM + && config.configType != EConfigType.POLICYGROUP && !Utils.isValidUrl(config.server) && !Utils.isPureIpAddress(config.server.orEmpty()) ) return diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt index b87c8dfb..2f3b7030 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt @@ -44,6 +44,8 @@ object V2rayConfigManager { val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false) return if (config.configType == EConfigType.CUSTOM) { getV2rayCustomConfig(guid, config) + } else if (config.configType == EConfigType.POLICYGROUP) { + getV2rayGroupConfig(context, guid, config) } else { getV2rayNormalConfig(context, guid, config) } @@ -53,27 +55,6 @@ object V2rayConfigManager { } } - /** - * Generates a V2ray configuration from multiple server profiles. - * - * @param context The context of the caller. - * @param guidList A list of server GUIDs to be included in the generated configuration. - * Each GUID represents a unique server profile stored in the system. - * @return A V2rayConfig object containing the combined configuration of all specified servers, - * or null if the operation fails (e.g., no valid configurations found, parsing errors) - */ - fun genV2rayConfig(context: Context, guidList: List): V2rayConfig? { - try { - val configList = guidList.mapNotNull { guid -> - MmkvManager.decodeServerConfig(guid) - } - return genV2rayMultipleConfig(context, configList) - } catch (e: Exception) { - Log.e(AppConfig.TAG, "Failed to generate V2ray config", e) - return null - } - } - /** * Retrieves the speedtest V2ray configuration for the given GUID. * @@ -86,6 +67,9 @@ object V2rayConfigManager { val config = MmkvManager.decodeServerConfig(guid) ?: return ConfigResult(false) return if (config.configType == EConfigType.CUSTOM) { getV2rayCustomConfig(guid, config) + } else if (config.configType == EConfigType.POLICYGROUP) { + // The number of policy groups will not be very large, so no special handling is needed. + getV2rayGroupConfig(context, guid, config) } else { getV2rayNormalConfig4Speedtest(context, guid, config) } @@ -107,6 +91,50 @@ object V2rayConfigManager { return ConfigResult(true, guid, raw) } + /** + * Retrieves the group V2ray configuration. + * + * @param context The context in which the function is called. + * @param guid The unique identifier for the V2ray configuration. + * @param config The profile item containing the configuration details. + * @return A ConfigResult object containing the result of the configuration retrieval. + */ + private fun getV2rayGroupConfig(context: Context, guid: String, config: ProfileItem): ConfigResult { + val result = ConfigResult(false) + + val serverList = MmkvManager.decodeServerList() + val configList = serverList + .mapNotNull { id -> MmkvManager.decodeServerConfig(id) } + .filter { profile -> + val subscriptionId = config.policyGroupSubscriptionId + if (subscriptionId.isNullOrBlank()) { + true + } else { + profile.subscriptionId == subscriptionId + } + } + .filter { profile -> + val filter = config.policyGroupFilter + if (filter.isNullOrBlank()) { + true + } else { + try { + Regex(filter).containsMatchIn(profile.remarks) + } catch (e: Exception) { + profile.remarks.contains(filter) + } + } + } + + val v2rayConfig = getV2rayMultipleConfig(context, config, configList) ?: return result + + result.status = true + result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: "" + result.guid = guid + + return result + } + /** * Retrieves the normal V2ray configuration. * @@ -164,21 +192,13 @@ object V2rayConfigManager { return result } - private fun genV2rayMultipleConfig(context: Context, configList: List): V2rayConfig? { + private fun getV2rayMultipleConfig(context: Context, config: ProfileItem, configList: List): V2rayConfig? { val validConfigs = configList.asSequence().filter { it.server.isNotNullEmpty() } .filter { !Utils.isPureIpAddress(it.server!!) || Utils.isValidUrl(it.server!!) } .filter { it.configType != EConfigType.CUSTOM } .filter { it.configType != EConfigType.HYSTERIA2 } - .filter { config -> - if (config.subscriptionId.isEmpty()) { - return@filter true - } - val subItem = MmkvManager.decodeSubscription(config.subscriptionId) - if (subItem?.intelligentSelectionFilter.isNullOrEmpty() || config.remarks.isEmpty()) { - return@filter true - } - Regex(pattern = subItem.intelligentSelectionFilter.orEmpty()).containsMatchIn(input = config.remarks) - }.toList() + .filter { it.configType != EConfigType.POLICYGROUP } + .toList() if (validConfigs.isEmpty()) { Log.w(AppConfig.TAG, "All configs are invalid") @@ -187,16 +207,7 @@ object V2rayConfigManager { val v2rayConfig = initV2rayConfig(context) ?: return null v2rayConfig.log.loglevel = MmkvManager.decodeSettingsString(AppConfig.PREF_LOGLEVEL) ?: "warning" - - val subIds = configList.map { it.subscriptionId }.toHashSet() - val remarks = if (subIds.size == 1 && subIds.first().isNotEmpty()) { - val sub = MmkvManager.decodeSubscription(subIds.first()) - (sub?.remarks ?: "") + context.getString(R.string.intelligent_selection) - } else { - context.getString(R.string.intelligent_selection) - } - - v2rayConfig.remarks = remarks + v2rayConfig.remarks = config.remarks getInbounds(v2rayConfig) @@ -220,12 +231,12 @@ object V2rayConfigManager { getDns(v2rayConfig) - getBalance(v2rayConfig) + getBalance(v2rayConfig, config) - if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) { + if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) { getCustomLocalDns(v2rayConfig) } - if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) { + if (!MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED)) { v2rayConfig.stats = null v2rayConfig.policy = null } @@ -469,7 +480,7 @@ object V2rayConfigManager { ) } - if (MmkvManager.decodeSettingsBool(AppConfig.PREF_USE_HEV_TUNNEL ,true) == false) { + if (MmkvManager.decodeSettingsBool(AppConfig.PREF_USE_HEV_TUNNEL, true) == false) { // DNS inbound val remoteDns = SettingsManager.getRemoteDnsServers() @@ -850,8 +861,7 @@ object V2rayConfigManager { * * @param v2rayConfig The V2ray configuration object to be modified with balancing settings */ - private fun getBalance(v2rayConfig: V2rayConfig) - { + private fun getBalance(v2rayConfig: V2rayConfig, config: ProfileItem) { try { v2rayConfig.routing.rules.forEach { rule -> if (rule.outboundTag == "proxy") { @@ -860,7 +870,7 @@ object V2rayConfigManager { } } - if (MmkvManager.decodeSettingsString(AppConfig.PREF_INTELLIGENT_SELECTION_METHOD, "0") == "0") { + if (config.policyGroupType == "0") { val balancer = V2rayConfig.RoutingBean.BalancerBean( tag = "proxy-round", selector = listOf("proxy-"), @@ -1054,6 +1064,7 @@ object V2rayConfigManager { EConfigType.WIREGUARD -> WireguardFmt.toOutbound(profileItem) EConfigType.HYSTERIA2 -> null EConfigType.HTTP -> HttpFmt.toOutbound(profileItem) + EConfigType.POLICYGROUP -> null } } @@ -1104,6 +1115,7 @@ object V2rayConfigManager { ) EConfigType.CUSTOM -> null + EConfigType.POLICYGROUP -> null } } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt index 9f8a2fc5..51d6c96f 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt @@ -313,6 +313,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList true } + R.id.import_manually_policy_group -> { + importManually(EConfigType.POLICYGROUP.value) + true + } + R.id.import_manually_vmess -> { importManually(EConfigType.VMESS.value) true @@ -370,14 +375,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList true } - R.id.intelligent_selection_all -> { - if (MmkvManager.decodeSettingsString(AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, "1") != "0") { - toast(getString(R.string.pre_resolving_domain)) - } - mainViewModel.createIntelligentSelectionAll() - true - } - R.id.service_restart -> { restartV2Ray() true @@ -413,12 +410,20 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList } private fun importManually(createConfigType: Int) { - startActivity( - Intent() - .putExtra("createConfigType", createConfigType) - .putExtra("subscriptionId", mainViewModel.subscriptionId) - .setClass(this, ServerActivity::class.java) - ) + if (createConfigType == EConfigType.POLICYGROUP.value) { + startActivity( + Intent() + .putExtra("subscriptionId", mainViewModel.subscriptionId) + .setClass(this, ServerGroupActivity::class.java) + ) + } else { + startActivity( + Intent() + .putExtra("createConfigType", createConfigType) + .putExtra("subscriptionId", mainViewModel.subscriptionId) + .setClass(this, ServerActivity::class.java) + ) + } } /** diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt index 7f27fd6f..43f32af7 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -57,7 +57,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter setContentView(R.layout.activity_server_vmess) EConfigType.CUSTOM -> return @@ -147,6 +147,7 @@ class ServerActivity : BaseActivity() { EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan) EConfigType.WIREGUARD -> setContentView(R.layout.activity_server_wireguard) EConfigType.HYSTERIA2 -> setContentView(R.layout.activity_server_hysteria2) + EConfigType.POLICYGROUP -> return } sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected( diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerCustomConfigActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerCustomConfigActivity.kt index 44e0b32b..ea0d3656 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerCustomConfigActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerCustomConfigActivity.kt @@ -32,7 +32,7 @@ class ServerCustomConfigActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) - title = getString(R.string.title_server) + title = EConfigType.CUSTOM.toString() if (!Utils.getDarkModeStatus(this)) { binding.editor.colorScheme = EditorTheme.INTELLIJ_LIGHT diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerGroupActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerGroupActivity.kt new file mode 100644 index 00000000..9ca96b28 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/ServerGroupActivity.kt @@ -0,0 +1,165 @@ +package com.v2ray.ang.ui + +import android.os.Bundle +import android.text.TextUtils +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.app.AlertDialog +import com.v2ray.ang.R +import com.v2ray.ang.databinding.ActivityServerGroupBinding +import com.v2ray.ang.dto.EConfigType +import com.v2ray.ang.dto.ProfileItem +import com.v2ray.ang.extension.toast +import com.v2ray.ang.extension.toastSuccess +import android.widget.ArrayAdapter +import com.v2ray.ang.handler.MmkvManager +import com.v2ray.ang.util.Utils + +class ServerGroupActivity : BaseActivity() { + private val binding by lazy { ActivityServerGroupBinding.inflate(layoutInflater) } + + private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() } + private val isRunning by lazy { + intent.getBooleanExtra("isRunning", false) + && editGuid.isNotEmpty() + && editGuid == MmkvManager.getSelectServer() + } + private val subscriptionId by lazy { + intent.getStringExtra("subscriptionId") + } + private val subIds = mutableListOf() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + title = EConfigType.POLICYGROUP.toString() + + val config = MmkvManager.decodeServerConfig(editGuid) + populateSubscriptionSpinner() + + if (config != null) { + bindingServer(config) + } else { + clearServer() + } + } + + /** + * Binding selected server config + */ + private fun bindingServer(config: ProfileItem): Boolean { + binding.etRemarks.text = Utils.getEditable(config.remarks) + binding.etPolicyGroupFilter.text = Utils.getEditable(config.policyGroupFilter) + + val type = config.policyGroupType?.toInt() ?: 0 + binding.spPolicyGroupType.setSelection(type) + + val pos = subIds.indexOf(config.policyGroupSubscriptionId ?: "").let { if (it >= 0) it else 0 } + binding.spPolicyGroupSubId.setSelection(pos) + + return true + } + + /** + * clear or init server config + */ + private fun clearServer(): Boolean { + binding.etRemarks.text = null + binding.etPolicyGroupFilter.text = null + return true + } + + /** + * save server config + */ + private fun saveServer(): Boolean { + if (TextUtils.isEmpty(binding.etRemarks.text.toString())) { + toast(R.string.server_lab_remarks) + return false + } + + val config = MmkvManager.decodeServerConfig(editGuid) ?: ProfileItem.create(EConfigType.POLICYGROUP) + config.remarks = binding.etRemarks.text.toString().trim() + config.policyGroupFilter = binding.etPolicyGroupFilter.text.toString().trim() + + config.policyGroupType = binding.spPolicyGroupType.selectedItemPosition.toString() + + val selPos = binding.spPolicyGroupSubId.selectedItemPosition + config.policyGroupSubscriptionId = if (selPos >= 0 && selPos < subIds.size) subIds[selPos] else null + + if (config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) { + config.subscriptionId = subscriptionId.orEmpty() + } + MmkvManager.encodeServerConfig(editGuid, config) + toastSuccess(R.string.toast_success) + finish() + return true + } + + /** + * save server config + */ + private fun deleteServer(): Boolean { + if (editGuid.isNotEmpty()) { + AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm) + .setPositiveButton(android.R.string.ok) { _, _ -> + MmkvManager.removeServer(editGuid) + finish() + } + .setNegativeButton(android.R.string.cancel) { _, _ -> + // do nothing + } + .show() + } + return true + } + + private fun populateSubscriptionSpinner() { + val subs = MmkvManager.decodeSubscriptions() + val displayList = mutableListOf(getString(R.string.filter_config_all)) //none + subIds.clear() + subIds.add("") // index 0 => All + subs.forEach { (id, item) -> + val name = when { + item.remarks.isNotBlank() -> item.remarks + else -> id + } + displayList.add(name) + subIds.add(id) + } + val subAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, displayList) + subAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.spPolicyGroupSubId.adapter = subAdapter + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.action_server, menu) + val delButton = menu.findItem(R.id.del_config) + val saveButton = menu.findItem(R.id.save_config) + + if (editGuid.isNotEmpty()) { + if (isRunning) { + delButton?.isVisible = false + saveButton?.isVisible = false + } + } else { + delButton?.isVisible = false + } + + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.del_config -> { + deleteServer() + true + } + + R.id.save_config -> { + saveServer() + true + } + + else -> super.onOptionsItemSelected(item) + } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt index 9b6107ca..385e5632 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt @@ -285,7 +285,6 @@ class SettingsActivity : BaseActivity() { AppConfig.PREF_UI_MODE_NIGHT, AppConfig.PREF_LOGLEVEL, AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, - AppConfig.PREF_INTELLIGENT_SELECTION_METHOD, AppConfig.PREF_MODE, AppConfig.PREF_HEV_TUNNEL_LOGLEVEL ).forEach { key -> diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt index 022e95ba..f342628d 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt @@ -46,7 +46,6 @@ class SubEditActivity : BaseActivity() { binding.etUrl.text = Utils.getEditable(subItem.url) binding.etUserAgent.text = Utils.getEditable(subItem.userAgent) binding.etFilter.text = Utils.getEditable(subItem.filter) - binding.etIntelligentSelectionFilter.text = Utils.getEditable(subItem.intelligentSelectionFilter) binding.chkEnable.isChecked = subItem.enabled binding.autoUpdateCheck.isChecked = subItem.autoUpdate binding.allowInsecureUrl.isChecked = subItem.allowInsecureUrl @@ -62,7 +61,6 @@ class SubEditActivity : BaseActivity() { binding.etRemarks.text = null binding.etUrl.text = null binding.etFilter.text = null - binding.etIntelligentSelectionFilter.text = null binding.chkEnable.isChecked = true binding.etPreProfile.text = null binding.etNextProfile.text = null @@ -79,7 +77,6 @@ class SubEditActivity : BaseActivity() { subItem.url = binding.etUrl.text.toString() subItem.userAgent = binding.etUserAgent.text.toString() subItem.filter = binding.etFilter.text.toString() - subItem.intelligentSelectionFilter = binding.etIntelligentSelectionFilter.text.toString() subItem.enabled = binding.chkEnable.isChecked subItem.autoUpdate = binding.autoUpdateCheck.isChecked subItem.prevProfile = binding.etPreProfile.text.toString() diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt index f9323c98..ec5cb7ee 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt @@ -384,29 +384,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { MmkvManager.encodeServerList(serverList) } - /** - * Creates an intelligent selection configuration containing all currently filtered servers. - */ - fun createIntelligentSelectionAll() { - viewModelScope.launch(Dispatchers.IO) { - val key = AngConfigManager.createIntelligentSelection( - getApplication(), - serversCache.map { it.guid }.toList(), - subscriptionId - ) - - launch(Dispatchers.Main) { - if (key.isNullOrEmpty()) { - getApplication().toastError(R.string.toast_failure) - } else { - getApplication().toastSuccess(R.string.toast_success) - MmkvManager.setSelectServer(key) - reloadServerList() - } - } - } - } - /** * Initializes assets. * @param assets The asset manager. diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SettingsViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SettingsViewModel.kt index 5e211e96..441afcb2 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SettingsViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SettingsViewModel.kt @@ -51,7 +51,6 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application AppConfig.PREF_SOCKS_PORT, AppConfig.PREF_LOGLEVEL, AppConfig.PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD, - AppConfig.PREF_INTELLIGENT_SELECTION_METHOD, AppConfig.PREF_LANGUAGE, AppConfig.PREF_UI_MODE_NIGHT, AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, diff --git a/V2rayNG/app/src/main/res/layout/activity_server_custom_config.xml b/V2rayNG/app/src/main/res/layout/activity_server_custom_config.xml index d6d587b8..5dabff6b 100644 --- a/V2rayNG/app/src/main/res/layout/activity_server_custom_config.xml +++ b/V2rayNG/app/src/main/res/layout/activity_server_custom_config.xml @@ -9,12 +9,12 @@ + android:orientation="vertical" + android:padding="@dimen/padding_spacing_dp16"> @@ -73,7 +73,6 @@ android:id="@+id/editor" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginTop="@dimen/padding_spacing_dp16" android:gravity="top|start" /> diff --git a/V2rayNG/app/src/main/res/layout/activity_server_group.xml b/V2rayNG/app/src/main/res/layout/activity_server_group.xml new file mode 100644 index 00000000..74e861bb --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_server_group.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml b/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml index c3db6a96..87a44c58 100644 --- a/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml +++ b/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml @@ -112,25 +112,6 @@ - - - - - - - - + - استيراد التكوين من رمز الاستجابة السريعة (QRcode) استيراد التكوين من الحافظة Import config from locally + Add [Policy group] الكتابة يدويًا [VMess] الكتابة يدويًا [VLESS] الكتابة يدويًا [Shadowsocks] @@ -278,7 +279,6 @@ تحديث الاشتراك (أول خطوة) Tcping لجميع الإعدادات اختبر جميع الإعدادات (3) - Creating Intelligent Selection Current Group Configuration Asset files الفرز حسب نتائج الاختبار (5) تصفية ملف التكوين @@ -332,6 +332,10 @@ Check Pre-release Checking for update… + Policy group type + From subscription group + Remarks regular filter + رمز استجابة سريعة (QRcode) تصدير إلى الحافظة @@ -373,10 +377,7 @@ Resolve and add to DNS Hosts Resolve and replace domain - Intelligent Selection - Remarks Intelligent Selection regular filter - Intelligent Selection Method - + Least Ping Least Load diff --git a/V2rayNG/app/src/main/res/values-bn/strings.xml b/V2rayNG/app/src/main/res/values-bn/strings.xml index 3b74dc02..7fc1692c 100644 --- a/V2rayNG/app/src/main/res/values-bn/strings.xml +++ b/V2rayNG/app/src/main/res/values-bn/strings.xml @@ -29,6 +29,7 @@ QR কোড থেকে কনফিগারেশন আমদানি করুন ক্লিপবোর্ড থেকে কনফিগারেশন আমদানি করুন Import config from locally + Add [Policy group] ম্যানুয়ালি টাইপ করুন [VMess] ম্যানুয়ালি টাইপ করুন [VLESS] ম্যানুয়ালি টাইপ করুন [Shadowsocks] @@ -278,7 +279,6 @@ সাবস্ক্রিপশন আপডেট সব কনফিগারেশন TCPing সব কনফিগারেশন প্রকৃত বিলম্ব - Creating Intelligent Selection Current Group Configuration Asset files টেস্ট ফলাফল দ্বারা সাজানো কনফিগারেশন ফাইল ফিল্টার করুন @@ -331,6 +331,10 @@ Check Pre-release Checking for update… + Policy group type + From subscription group + Remarks regular filter + QR কোড ক্লিপবোর্ডে রপ্তানি করুন @@ -378,10 +382,7 @@ Resolve and add to DNS Hosts Resolve and replace domain - Intelligent Selection - Remarks Intelligent Selection regular filter - Intelligent Selection Method - + Least Ping Least Load diff --git a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml index 32651a18..fc922c32 100644 --- a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml +++ b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml @@ -29,6 +29,7 @@ و من ٱووردن کانفیگ ز QRcode و من ٱووردن کانفیگ ز کلیپ بورد و من ٱووردن کانفیگ ز مهلی + Add [Policy group] هؽل دستی[VMess] هؽل دستی[VLESS] هؽل دستی[Shadowsocks] @@ -278,7 +279,6 @@ ورۊ کردن اشتراک بونکۊ سکویی Tcping کانفیگا بونکۊ سکویی تئخیر واقعی کانفیگا بونکۊ سکویی - وورکل پسند هۊشمند کانفیگ بونکۊ سکویی فایلا بونچک جوقرافیایی ترتیب و ری نتیجه یل آزمایش فیلتر کردن کانفیگا @@ -340,6 +340,10 @@ واجۊری نوسخه یل پؽش ز تیجنیڌن ورۊ رسۊوی ن هونی واجۊری اکونه... + Policy group type + From subscription group + Remarks regular filter + QRcode و در کشیڌن من کلیپ بورد @@ -388,10 +392,7 @@ هل وو ٱووردن و میزبووݩ یل دامنه DNS هل وو جایونی دامنه - پسند هۊشمند - گوڌنا دیاری پسند هۊشمند فیلتر مئمۊلی - بارت پسند هۊشمند - + کم ترین پینگ کم ترین بار(لود) diff --git a/V2rayNG/app/src/main/res/values-fa/strings.xml b/V2rayNG/app/src/main/res/values-fa/strings.xml index 69f54a16..27ecd931 100644 --- a/V2rayNG/app/src/main/res/values-fa/strings.xml +++ b/V2rayNG/app/src/main/res/values-fa/strings.xml @@ -29,6 +29,7 @@ کانفیگ را از QRcode وارد کنید کانفیگ را از کلیپ ‌بورد وارد کنید کانفیگ را از محلی وارد کنید + Add [Policy group] تایپ دستی[VMESS] تایپ دستی[VLESS] تایپ دستی[SHADOWSOCKS] @@ -275,7 +276,6 @@ به‌روزرسانی گروه فعلی اشتراک TCPING کانفیگ های گروه فعلی تاخیر واقعی کانفیگ های گروه فعلی - ایجاد کانفیگ انتخاب هوشمند برای گروه فعلی فایل های منبع جغرافیایی مرتب‌ سازی بر اساس نتایج آزمایش فیلتر کردن کانفیگ‌ها @@ -337,6 +337,10 @@ بررسی نسخه پیش از انتشار در حال بررسی برای به‌روزرسانی… + Policy group type + From subscription group + Remarks regular filter + QRcode خروجی گرفتن در کلیپ‌ بورد @@ -387,10 +391,7 @@ Resolve and add to DNS Hosts Resolve and replace domain - انتخاب هوشمند - فیلتر نام مستعار برای انتخاب هوشمند - روش انتخاب هوشمند - + کمترین پینگ کمترین بار(لود) diff --git a/V2rayNG/app/src/main/res/values-ru/strings.xml b/V2rayNG/app/src/main/res/values-ru/strings.xml index 1c2ac143..84555bd8 100644 --- a/V2rayNG/app/src/main/res/values-ru/strings.xml +++ b/V2rayNG/app/src/main/res/values-ru/strings.xml @@ -29,6 +29,7 @@ Импорт из QR-кода Импорт из буфера обмена Импорт из файла + Add [Policy group] Ручной ввод VMess Ручной ввод VLESS Ручной ввод Shadowsocks @@ -277,7 +278,6 @@ Обновить подписку группы Проверить профили группы Проверить задержку профилей группы - Создать конфигурацию умного выбора Файлы ресурсов Сортировать по результатам теста Фильтр профилей @@ -339,6 +339,10 @@ Искать предварительный выпуск Проверка обновления… + Policy group type + From subscription group + Remarks regular filter + QR-код Экспорт в буфер обмена @@ -387,10 +391,7 @@ Определять и добавлять к узлам DNS Определять и заменять домен - Умный выбор - Название фильтра умного выбора - Принцип умного выбора - + Наименьшая задержка Наименьшая нагрузка diff --git a/V2rayNG/app/src/main/res/values-vi/strings.xml b/V2rayNG/app/src/main/res/values-vi/strings.xml index 749b9516..f377976f 100644 --- a/V2rayNG/app/src/main/res/values-vi/strings.xml +++ b/V2rayNG/app/src/main/res/values-vi/strings.xml @@ -29,6 +29,7 @@ Nhập cấu hình từ mã QR Nhập cấu hình từ Clipboard Import config from locally + Add [Policy group] Nhập thủ công [VMess] Nhập thủ công [VLESS] Nhập thủ công [ShadowSocks] @@ -278,7 +279,6 @@ Cập nhật các gói đăng ký Ping tất cả máy chủ Kiểm tra HTTP tất cả máy chủ - Creating Intelligent Selection Current Group Configuration Asset files Sắp xếp lại theo lần kiểm tra cuối cùng Lọc cấu hình theo các gói đăng ký @@ -333,6 +333,10 @@ Check Pre-release Checking for update… + Policy group type + From subscription group + Remarks regular filter + Xuất ra mã QR (Chụp màn hình để lưu) Sao chép vào Clipboard @@ -375,10 +379,7 @@ Resolve and add to DNS Hosts Resolve and replace domain - Intelligent Selection - Remarks Intelligent Selection regular filter - Intelligent Selection Method - + Least Ping Least Load diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index d109cbfc..2fb96a25 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -29,14 +29,15 @@ 扫描二维码 从剪贴板导入 从本地导入 - 手动输入 [VMess] - 手动输入 [VLESS] - 手动输入 [Shadowsocks] - 手动输入 [SOCKS] - 手动输入 [HTTP] - 手动输入 [Trojan] - 手动输入 [Wireguard] - 手动输入 [Hysteria2] + 添加 [策略组] + 添加 [VMess] + 添加 [VLESS] + 添加 [Shadowsocks] + 添加 [SOCKS] + 添加 [HTTP] + 添加 [Trojan] + 添加 [Wireguard] + 添加 [Hysteria2] 确认删除? 删除前请先测试!确认删除? 别名 (remarks) @@ -275,7 +276,6 @@ 更新当前组订阅 测试当前组配置 Tcping 测试当前组配置真连接 - 生成当前组智能选择配置 资源文件 按测试结果排序 过滤配置 @@ -331,6 +331,10 @@ 检查 Pre-release 正在检查更新中… + 策略组类型 + 来自订阅分组 + 别名正则过滤 + 二维码 导出至剪贴板 @@ -379,10 +383,7 @@ 解析后添加至 DNS Hosts 解析后替换原域名 - 智能选择 - 别名智能选择正则过滤 - 智能选择方式 - + 最低延迟 最稳定 diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index e53cb7b4..aa14cc09 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -29,14 +29,15 @@ 從 QR Code 匯入設定 從剪貼簿匯入設定 從本地匯入 - 手動鍵入 [VMess] - 手動鍵入 [VLESS] - 手動鍵入 [Shadowsocks] - 手動鍵入 [SOCKS] - 手動鍵入 [HTTP] - 手動鍵入 [Trojan] - 手動鍵入 [Wireguard] - 手動鍵入 [Hysteria2] + 新增 [策略組] + 新增 [VMess] + 新增 [VLESS] + 新增 [Shadowsocks] + 新增 [SOCKS] + 新增 [HTTP] + 新增 [Trojan] + 新增 [Wireguard] + 新增 [Hysteria2] 確定刪除? 刪除前請先測試!確認刪除? 備註 @@ -276,7 +277,6 @@ 更新目前群組訂閱 偵測目前群組設定 Tcping 偵測目前群組設定真延遲 - 創建目前群組智慧選擇 資源檔案 依偵測結果排序 過濾設定 @@ -331,6 +331,10 @@ 檢查 Pre-release 正在檢查更新中… + 策略群組類型 + 來自訂閱分組 + 別名正規過濾 + 二維碼 匯出至剪貼簿 @@ -379,10 +383,7 @@ 解析後加入 DNS Hosts 解析後替換原網域名稱 - 智慧選擇 - 別名智慧選擇正則過濾 - 智慧選擇方案 - + 最低延遲 最少卡頓 diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index e8ef2476..1cdaede4 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -215,9 +215,4 @@ 2 - - 0 - 1 - - \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index b87b4bd3..e821aecc 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -27,17 +27,18 @@ Save configuration Edit configuration Delete configuration - Import configuration from QRcode - Import configuration from Clipboard - Import configuration from locally - Type manually[VMess] - Type manually[VLESS] - Type manually[Shadowsocks] - Type manually[SOCKS] - Type manually[HTTP] - Type manually[Trojan] - Type manually[Wireguard] - Type manually[Hysteria2] + Import from QRcode + Import from Clipboard + Import from locally + Add [Policy group] + Add [VMess] + Add [VLESS] + Add [Shadowsocks] + Add [SOCKS] + Add [HTTP] + Add [Trojan] + Add [Wireguard] + Add [Hysteria2] Confirm delete ? Please test before deleting! Confirm delete ? remarks @@ -281,7 +282,6 @@ Update current group subscription Tcping current group configuration Real delay current group configuration - Creating Intelligent Selection Current Group Configuration Asset files Sorting by test results Filter configuration @@ -343,6 +343,10 @@ Check Pre-release Checking for update… + Policy group type + From subscription group + Remarks regular filter + QRcode Export to clipboard @@ -391,13 +395,12 @@ Resolve and add to DNS Hosts Resolve and replace domain - Intelligent Selection - Remarks Intelligent Selection regular filter - Intelligent Selection Method - + + Least Ping Least Load + Pre-resolving domain… diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index 029af7b5..7267234a 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -256,14 +256,6 @@ android:summary="%s" android:title="@string/title_outbound_domain_resolve_method" /> - -