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" />
-
-