From 0b065c745db362d1df716ccdbc168634684dd374 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Sat, 21 Sep 2024 15:36:45 +0800 Subject: [PATCH] Refactor routing function --- V2rayNG/app/src/main/AndroidManifest.xml | 6 +- .../app/src/main/assets/custom_routing_black | 73 +++++++ .../app/src/main/assets/custom_routing_global | 34 +++ .../app/src/main/assets/custom_routing_white | 81 +++++++ .../kotlin/com/v2ray/ang/AngApplication.kt | 3 + .../main/kotlin/com/v2ray/ang/AppConfig.kt | 6 +- .../kotlin/com/v2ray/ang/dto/ERoutingMode.kt | 9 - .../kotlin/com/v2ray/ang/dto/RulesetItem.kt | 12 ++ .../com/v2ray/ang/service/V2RayVpnService.kt | 10 +- .../kotlin/com/v2ray/ang/ui/MainActivity.kt | 5 +- .../com/v2ray/ang/ui/RoutingEditActivity.kt | 127 +++++++++++ .../v2ray/ang/ui/RoutingSettingActivity.kt | 101 +++++++++ .../ang/ui/RoutingSettingRecyclerAdapter.kt | 51 +++++ .../v2ray/ang/ui/RoutingSettingsActivity.kt | 35 ---- .../v2ray/ang/ui/RoutingSettingsFragment.kt | 170 --------------- .../com/v2ray/ang/ui/SettingsActivity.kt | 8 - .../com/v2ray/ang/util/AngConfigManager.kt | 1 - .../kotlin/com/v2ray/ang/util/MmkvManager.kt | 15 ++ .../com/v2ray/ang/util/SettingsManager.kt | 73 +++++++ .../com/v2ray/ang/util/V2rayConfigUtil.kt | 198 +++--------------- .../v2ray/ang/viewmodel/SettingsViewModel.kt | 4 - .../res/drawable-night/ic_routing_24dp.xml | 9 + .../src/main/res/drawable/ic_routing_24dp.xml | 9 + .../main/res/layout/activity_routing_edit.xml | 174 +++++++++++++++ .../res/layout/activity_routing_setting.xml | 70 +++++++ .../res/layout/activity_routing_settings.xml | 16 -- .../src/main/res/layout/activity_sub_edit.xml | 3 +- .../res/layout/fragment_routing_settings.xml | 39 ---- .../layout/item_recycler_routing_setting.xml | 104 +++++++++ .../res/layout/item_recycler_sub_setting.xml | 2 +- V2rayNG/app/src/main/res/menu/menu_drawer.xml | 6 +- .../app/src/main/res/menu/menu_routing.xml | 31 --- .../main/res/menu/menu_routing_setting.xml | 18 ++ .../app/src/main/res/values-ar/strings.xml | 27 +-- .../app/src/main/res/values-bn/strings.xml | 27 +-- .../app/src/main/res/values-fa/strings.xml | 27 +-- .../app/src/main/res/values-ru/strings.xml | 27 +-- .../app/src/main/res/values-vi/strings.xml | 27 +-- .../src/main/res/values-zh-rCN/strings.xml | 30 +-- .../src/main/res/values-zh-rTW/strings.xml | 29 +-- V2rayNG/app/src/main/res/values/arrays.xml | 14 +- V2rayNG/app/src/main/res/values/dimens.xml | 2 +- V2rayNG/app/src/main/res/values/strings.xml | 37 ++-- .../app/src/main/res/xml/pref_settings.xml | 24 --- 44 files changed, 1059 insertions(+), 715 deletions(-) create mode 100644 V2rayNG/app/src/main/assets/custom_routing_black create mode 100644 V2rayNG/app/src/main/assets/custom_routing_global create mode 100644 V2rayNG/app/src/main/assets/custom_routing_white delete mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/ERoutingMode.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/RulesetItem.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingEditActivity.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingActivity.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingRecyclerAdapter.kt delete mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsActivity.kt delete mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt create mode 100644 V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/SettingsManager.kt create mode 100644 V2rayNG/app/src/main/res/drawable-night/ic_routing_24dp.xml create mode 100644 V2rayNG/app/src/main/res/drawable/ic_routing_24dp.xml create mode 100644 V2rayNG/app/src/main/res/layout/activity_routing_edit.xml create mode 100644 V2rayNG/app/src/main/res/layout/activity_routing_setting.xml delete mode 100644 V2rayNG/app/src/main/res/layout/activity_routing_settings.xml delete mode 100644 V2rayNG/app/src/main/res/layout/fragment_routing_settings.xml create mode 100644 V2rayNG/app/src/main/res/layout/item_recycler_routing_setting.xml delete mode 100644 V2rayNG/app/src/main/res/menu/menu_routing.xml create mode 100644 V2rayNG/app/src/main/res/menu/menu_routing_setting.xml diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index 6021d476..bae5335a 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -98,8 +98,10 @@ android:name=".ui.LogcatActivity" /> + android:name=".ui.RoutingSettingActivity" /> + diff --git a/V2rayNG/app/src/main/assets/custom_routing_black b/V2rayNG/app/src/main/assets/custom_routing_black new file mode 100644 index 00000000..e394a495 --- /dev/null +++ b/V2rayNG/app/src/main/assets/custom_routing_black @@ -0,0 +1,73 @@ +[ + { + "remarks": "绕过bittorrent", + "outboundTag": "direct", + "protocol": [ + "bittorrent" + ] + }, + { + "remarks": "Google cn", + "outboundTag": "proxy", + "domain": [ + "domain:googleapis.cn", + "domain:gstatic.com" + ] + }, + { + "remarks": "阻断udp443", + "outboundTag": "block", + "port": "443", + "network": "udp" + }, + { + "remarks": "阻断广告", + "outboundTag": "block", + "domain": [ + "geosite:category-ads-all" + ] + }, + { + "remarks": "绕过局域网域名", + "outboundTag": "direct", + "domain": [ + "geosite:private" + ] + }, + { + "remarks": "绕过局域网IP", + "outboundTag": "direct", + "ip": [ + "geoip:private" + ] + }, + { + "remarks": "代理GFW", + "outboundTag": "proxy", + "domain": [ + "geosite:gfw", + "geosite:greatfire" + ] + }, + { + "remarks": "代理Google等", + "outboundTag": "proxy", + "ip": [ + "1.0.0.1", + "1.1.1.1", + "8.8.8.8", + "8.8.4.4", + "geoip:facebook", + "geoip:fastly", + "geoip:google", + "geoip:netflix", + "geoip:telegram", + "geoip:twitter" + ] + }, + { + "remarks": "最终直连", + "port": "0-65535", + "outboundTag": "direct" + } +] \ No newline at end of file diff --git a/V2rayNG/app/src/main/assets/custom_routing_global b/V2rayNG/app/src/main/assets/custom_routing_global new file mode 100644 index 00000000..75e6051b --- /dev/null +++ b/V2rayNG/app/src/main/assets/custom_routing_global @@ -0,0 +1,34 @@ +[ + { + "remarks": "阻断udp443", + "outboundTag": "block", + "port": "443", + "network": "udp" + }, + { + "remarks": "阻断广告", + "outboundTag": "block", + "domain": [ + "geosite:category-ads-all" + ] + }, + { + "remarks": "绕过局域网域名", + "outboundTag": "direct", + "domain": [ + "geosite:private" + ] + }, + { + "remarks": "绕过局域网IP", + "outboundTag": "direct", + "ip": [ + "geoip:private" + ] + }, + { + "remarks": "最终代理", + "port": "0-65535", + "outboundTag": "proxy" + } +] \ No newline at end of file diff --git a/V2rayNG/app/src/main/assets/custom_routing_white b/V2rayNG/app/src/main/assets/custom_routing_white new file mode 100644 index 00000000..008120aa --- /dev/null +++ b/V2rayNG/app/src/main/assets/custom_routing_white @@ -0,0 +1,81 @@ +[ + { + "remarks": "Google cn", + "outboundTag": "proxy", + "domain": [ + "domain:googleapis.cn", + "domain:gstatic.com" + ] + }, + { + "remarks": "阻断udp443", + "outboundTag": "block", + "port": "443", + "network": "udp" + }, + { + "remarks": "阻断广告", + "outboundTag": "block", + "domain": [ + "geosite:category-ads-all" + ] + }, + { + "remarks": "绕过局域网域名", + "outboundTag": "direct", + "domain": [ + "geosite:private" + ] + }, + { + "remarks": "绕过局域网IP", + "outboundTag": "direct", + "ip": [ + "geoip:private" + ] + }, + { + "remarks": "绕过中国域名", + "outboundTag": "direct", + "domain": [ + "domain:dns.alidns.com", + "domain:doh.pub", + "domain:dot.pub", + "domain:doh.360.cn", + "domain:dot.360.cn", + "geosite:cn", + "geosite:geolocation-cn" + ] + }, + { + "remarks": "绕过中国IP", + "outboundTag": "direct", + "ip": [ + "223.5.5.5/32", + "223.6.6.6/32", + "2400:3200::1/128", + "2400:3200:baba::1/128", + "119.29.29.29/32", + "1.12.12.12/32", + "120.53.53.53/32", + "2402:4e00::/128", + "2402:4e00:1::/128", + "180.76.76.76/32", + "2400:da00::6666/128", + "114.114.114.114/32", + "114.114.115.115/32", + "180.184.1.1/32", + "180.184.2.2/32", + "101.226.4.6/32", + "218.30.118.6/32", + "123.125.81.6/32", + "140.207.198.6/32", + "geoip:cn" + ] + }, + { + "remarks": "最终代理", + "port": "0-65535", + "outboundTag": "proxy" + } +] \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt index a07e7f99..37b40f6e 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AngApplication.kt @@ -5,6 +5,7 @@ import androidx.multidex.MultiDexApplication import androidx.work.Configuration import androidx.work.WorkManager import com.tencent.mmkv.MMKV +import com.v2ray.ang.util.SettingsManager import com.v2ray.ang.util.Utils class AngApplication : MultiDexApplication() { @@ -38,5 +39,7 @@ class AngApplication : MultiDexApplication() { Utils.setNightMode(application) // Initialize WorkManager with the custom configuration WorkManager.initialize(this, workManagerConfiguration) + + SettingsManager.initRoutingRulesets(this) } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt index b564a41b..480b331b 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/AppConfig.kt @@ -25,11 +25,7 @@ object AppConfig { const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port" const val PREF_VPN_DNS = "pref_vpn_dns" const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy" - const val PREF_ROUTING_MODE = "pref_routing_mode" - const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent" - const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct" - const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked" - const val PREF_ROUTING_CUSTOM = "pref_routing_custom" + const val PREF_ROUTING_RULESET = "pref_routing_ruleset" const val PREF_MUX_ENABLED = "pref_mux_enabled" const val PREF_MUX_CONCURRENCY = "pref_mux_concurrency" const val PREF_MUX_XUDP_CONCURRENCY = "pref_mux_xudp_concurrency" diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/ERoutingMode.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/ERoutingMode.kt deleted file mode 100644 index 8d882ed6..00000000 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/ERoutingMode.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.v2ray.ang.dto - -enum class ERoutingMode(val value: String) { - GLOBAL_PROXY("0"), - BYPASS_LAN("1"), - BYPASS_MAINLAND("2"), - BYPASS_LAN_MAINLAND("3"), - GLOBAL_DIRECT("4"); -} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/RulesetItem.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/RulesetItem.kt new file mode 100644 index 00000000..42bf5edb --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/RulesetItem.kt @@ -0,0 +1,12 @@ +package com.v2ray.ang.dto + +data class RulesetItem( + var remarks: String? = "", + var ip: List? = null, + var domain: List? = null, + var outboundTag: String = "", + var port: String? = null, + var network: String? = null, + var protocol: List? = null, + var enabled: Boolean = true, +) \ No newline at end of file diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt index c53eb4d8..4c93bdc1 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/service/V2RayVpnService.kt @@ -18,9 +18,9 @@ import android.util.Log import androidx.annotation.RequiresApi import com.v2ray.ang.AppConfig import com.v2ray.ang.R -import com.v2ray.ang.dto.ERoutingMode import com.v2ray.ang.util.MmkvManager.settingsStorage import com.v2ray.ang.util.MyContextWrapper +import com.v2ray.ang.util.SettingsManager import com.v2ray.ang.util.Utils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -115,13 +115,11 @@ class V2RayVpnService : VpnService(), ServiceControl { val builder = Builder() //val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false) - val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) - ?: ERoutingMode.BYPASS_LAN_MAINLAND.value - builder.setMtu(VPN_MTU) builder.addAddress(PRIVATE_VLAN4_CLIENT, 30) //builder.addDnsServer(PRIVATE_VLAN4_ROUTER) - if (routingMode == ERoutingMode.BYPASS_LAN.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) { + val bypassLan = SettingsManager.routingRulesetsBypassLan() + if (bypassLan) { resources.getStringArray(R.array.bypass_private_ip_address).forEach { val addr = it.split('/') builder.addRoute(addr[0], addr[1].toInt()) @@ -132,7 +130,7 @@ class V2RayVpnService : VpnService(), ServiceControl { if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) { builder.addAddress(PRIVATE_VLAN6_CLIENT, 126) - if (routingMode == ERoutingMode.BYPASS_LAN.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) { + if (bypassLan) { builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use } else { builder.addRoute("::", 0) diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt index c27a8839..ffef8f4a 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/MainActivity.kt @@ -707,10 +707,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList ) } - R.id.user_asset_setting -> { - startActivity(Intent(this, UserAssetActivity::class.java)) + R.id.routing_setting -> { + requestSubSettingActivity.launch(Intent(this, RoutingSettingActivity::class.java)) } + R.id.promotion -> { Utils.openUri(this, "${Utils.decode(AppConfig.PromotionUrl)}?t=${System.currentTimeMillis()}") } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingEditActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingEditActivity.kt new file mode 100644 index 00000000..f7f20761 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingEditActivity.kt @@ -0,0 +1,127 @@ +package com.v2ray.ang.ui + +import android.os.Bundle +import android.text.TextUtils +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.lifecycleScope +import com.google.gson.Gson +import com.v2ray.ang.R +import com.v2ray.ang.databinding.ActivityRoutingEditBinding +import com.v2ray.ang.dto.RulesetItem +import com.v2ray.ang.extension.toast +import com.v2ray.ang.util.SettingsManager +import com.v2ray.ang.util.Utils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class RoutingEditActivity : BaseActivity() { + private val binding by lazy { ActivityRoutingEditBinding.inflate(layoutInflater) } + private val position by lazy { intent.getIntExtra("position", -1) } + + private val outbound_tag: Array by lazy { + resources.getStringArray(R.array.outbound_tag) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + title = getString(R.string.routing_settings_rule_title) + + val rulesetItem = SettingsManager.getRoutingRuleset(position) + if (rulesetItem != null) { + bindingServer(rulesetItem) + } else { + clearServer() + } + } + + private fun bindingServer(rulesetItem: RulesetItem): Boolean { + binding.etRemarks.text = Utils.getEditable(rulesetItem.remarks) + binding.etDomain.text = Utils.getEditable(rulesetItem.domain?.joinToString()) + binding.etIp.text = Utils.getEditable(rulesetItem.ip?.joinToString()) + binding.etPort.text = Utils.getEditable(rulesetItem.port) + binding.etProtocol.text = Utils.getEditable(rulesetItem.protocol?.joinToString()) + binding.etNetwork.text = Utils.getEditable(rulesetItem.network) + val outbound = Utils.arrayFind(outbound_tag, rulesetItem.outboundTag) + binding.spOutboundTag.setSelection(outbound) + + return true + } + + private fun clearServer(): Boolean { + binding.etRemarks.text = null + binding.spOutboundTag.setSelection(0) + return true + } + + private fun saveServer(): Boolean { + val rulesetItem = SettingsManager.getRoutingRuleset(position) ?: RulesetItem() + + rulesetItem.remarks = binding.etRemarks.text.toString() + binding.etDomain.text.toString().let { rulesetItem.domain = if (it.isEmpty()) null else it.split(',') } + binding.etIp.text.toString().let { rulesetItem.ip = if (it.isEmpty()) null else it.split(',') } + binding.etProtocol.text.toString().let { rulesetItem.protocol = if (it.isEmpty()) null else it.split(',') } + binding.etPort.text.toString().let { rulesetItem.port = it.ifEmpty { null } } + binding.etNetwork.text.toString().let { rulesetItem.network = it.ifEmpty { null } } + rulesetItem.outboundTag = outbound_tag[binding.spOutboundTag.selectedItemPosition] + + if (TextUtils.isEmpty(rulesetItem.remarks)) { + toast(R.string.sub_setting_remarks) + return false + } + + Log.d("====", Gson().toJson(rulesetItem)) + SettingsManager.saveRoutingRuleset(position, rulesetItem) + toast(R.string.toast_success) + finish() + return true + } + + private fun deleteServer(): Boolean { + if (position >= 0) { + AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm) + .setPositiveButton(android.R.string.ok) { _, _ -> + lifecycleScope.launch(Dispatchers.IO) { + SettingsManager.removeRoutingRuleset(position) + launch(Dispatchers.Main) { + finish() + } + } + } + .setNegativeButton(android.R.string.no) { _, _ -> + // do nothing + } + .show() + } + return true + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.action_server, menu) + val del_config = menu.findItem(R.id.del_config) + + if (position < 0) { + del_config?.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/kotlin/com/v2ray/ang/ui/RoutingSettingActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingActivity.kt new file mode 100644 index 00000000..22eb5de3 --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingActivity.kt @@ -0,0 +1,101 @@ +package com.v2ray.ang.ui + +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.AdapterView +import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import com.v2ray.ang.AppConfig +import com.v2ray.ang.R +import com.v2ray.ang.databinding.ActivityRoutingSettingBinding +import com.v2ray.ang.dto.RulesetItem +import com.v2ray.ang.extension.toast +import com.v2ray.ang.util.MmkvManager +import com.v2ray.ang.util.MmkvManager.settingsStorage +import com.v2ray.ang.util.SettingsManager +import com.v2ray.ang.util.Utils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class RoutingSettingActivity : BaseActivity() { + private val binding by lazy { ActivityRoutingSettingBinding.inflate(layoutInflater) } + + var rulesets: MutableList = mutableListOf() + private val adapter by lazy { RoutingSettingRecyclerAdapter(this) } + private val routing_domain_strategy: Array by lazy { + resources.getStringArray(R.array.routing_domain_strategy) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + + title = getString(R.string.routing_settings_title) + + binding.recyclerView.setHasFixedSize(true) + binding.recyclerView.layoutManager = LinearLayoutManager(this) + binding.recyclerView.adapter = adapter + + val found = Utils.arrayFind(routing_domain_strategy, settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) ?: "") + found.let { binding.spDomainStrategy.setSelection(if (it >= 0) it else 0) } + binding.spDomainStrategy.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) { + } + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + settingsStorage.encode(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, routing_domain_strategy[position]) + } + } + } + + override fun onResume() { + super.onResume() + refreshData() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_routing_setting, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.add_rule -> { + startActivity(Intent(this, RoutingEditActivity::class.java)) + true + } + + R.id.user_asset_setting -> { + startActivity(Intent(this, UserAssetActivity::class.java)) + true + } + + R.id.import_rulesets -> { + AlertDialog.Builder(this).setMessage(R.string.routing_settings_import_rulesets_tip) + .setPositiveButton(android.R.string.ok) { _, _ -> + lifecycleScope.launch(Dispatchers.IO) { + SettingsManager.resetRoutingRulesets(this@RoutingSettingActivity) + launch(Dispatchers.Main) { + refreshData() + toast(R.string.toast_success) + } + } + } + .setNegativeButton(android.R.string.no) { _, _ -> + //do noting + } + .show() + true + } + + else -> super.onOptionsItemSelected(item) + } + + private fun refreshData() { + rulesets = MmkvManager.decodeRoutingRulesets() ?: mutableListOf() + adapter.notifyDataSetChanged() + } +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingRecyclerAdapter.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingRecyclerAdapter.kt new file mode 100644 index 00000000..aecf7c5a --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingRecyclerAdapter.kt @@ -0,0 +1,51 @@ +package com.v2ray.ang.ui + +import android.content.Intent +import android.graphics.Color +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.v2ray.ang.databinding.ItemRecyclerRoutingSettingBinding +import com.v2ray.ang.util.SettingsManager + +class RoutingSettingRecyclerAdapter(val activity: RoutingSettingActivity) : + RecyclerView.Adapter() { + + private var mActivity: RoutingSettingActivity = activity + override fun getItemCount() = mActivity.rulesets.size + + override fun onBindViewHolder(holder: MainViewHolder, position: Int) { + val ruleset = mActivity.rulesets[position] + + holder.itemRoutingSettingBinding.remarks.text = ruleset.remarks + holder.itemRoutingSettingBinding.domainIp.text = (ruleset.domain ?: ruleset.ip ?: ruleset.port)?.toString() + holder.itemRoutingSettingBinding.outboundTag.text = ruleset.outboundTag + holder.itemRoutingSettingBinding.chkEnable.isChecked = ruleset.enabled + holder.itemView.setBackgroundColor(Color.TRANSPARENT) + + holder.itemRoutingSettingBinding.layoutEdit.setOnClickListener { + mActivity.startActivity( + Intent(mActivity, RoutingEditActivity::class.java) + .putExtra("position", position) + ) + } + + holder.itemRoutingSettingBinding.chkEnable.setOnCheckedChangeListener { _, isChecked -> + ruleset.enabled = isChecked + SettingsManager.saveRoutingRuleset(position, ruleset) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder { + return MainViewHolder( + ItemRecyclerRoutingSettingBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + class MainViewHolder(val itemRoutingSettingBinding: ItemRecyclerRoutingSettingBinding) : + RecyclerView.ViewHolder(itemRoutingSettingBinding.root) +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsActivity.kt deleted file mode 100644 index c8d84ab0..00000000 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsActivity.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.v2ray.ang.ui - -import android.os.Bundle -import androidx.fragment.app.Fragment -import com.google.android.material.tabs.TabLayoutMediator -import com.v2ray.ang.AppConfig -import com.v2ray.ang.R -import com.v2ray.ang.databinding.ActivityRoutingSettingsBinding - -class RoutingSettingsActivity : BaseActivity() { - private val binding by lazy { ActivityRoutingSettingsBinding.inflate(layoutInflater) } - - private val titles: Array by lazy { - resources.getStringArray(R.array.routing_tag) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(binding.root) - - title = getString(R.string.title_pref_routing_custom) - - val fragments = ArrayList() - fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_AGENT)) - fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_DIRECT)) - fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)) - - val adapter = FragmentAdapter(this, fragments) - binding.viewpager.adapter = adapter - //tablayout.setTabTextColors(Color.BLACK, Color.RED) - TabLayoutMediator(binding.tablayout, binding.viewpager) { tab, position -> - tab.text = titles[position] - }.attach() - } -} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt deleted file mode 100644 index 429ab3fb..00000000 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/RoutingSettingsFragment.kt +++ /dev/null @@ -1,170 +0,0 @@ -package com.v2ray.ang.ui - -import android.Manifest -import android.content.Intent -import android.os.Bundle -import android.text.TextUtils -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity.RESULT_OK -import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope -import com.tbruyelle.rxpermissions3.RxPermissions -import com.v2ray.ang.AppConfig -import com.v2ray.ang.R -import com.v2ray.ang.databinding.FragmentRoutingSettingsBinding -import com.v2ray.ang.extension.toast -import com.v2ray.ang.extension.v2RayApplication -import com.v2ray.ang.util.MmkvManager -import com.v2ray.ang.util.MmkvManager.settingsStorage -import com.v2ray.ang.util.Utils -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -class RoutingSettingsFragment : Fragment() { - private val binding by lazy { FragmentRoutingSettingsBinding.inflate(layoutInflater) } - - companion object { - private const val routing_arg = "routing_arg" - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - // Inflate the layout for this fragment - return binding.root// inflater.inflate(R.layout.fragment_routing_settings, container, false) - } - - fun newInstance(arg: String): Fragment { - val fragment = RoutingSettingsFragment() - val bundle = Bundle() - bundle.putString(routing_arg, arg) - fragment.arguments = bundle - return fragment - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val content = settingsStorage?.getString(requireArguments().getString(routing_arg), "") - binding.etRoutingContent.text = Utils.getEditable(content) - - setHasOptionsMenu(true) - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.menu_routing, menu) - return super.onCreateOptionsMenu(menu, inflater) - } - - override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { - R.id.save_routing -> { - saveRouting() - true - } - - R.id.del_routing -> { - binding.etRoutingContent.text = null - true - } - - R.id.scan_replace -> { - scanQRcode(true) - true - } - - R.id.scan_append -> { - scanQRcode(false) - true - } - - R.id.default_rules -> { - setDefaultRules() - true - } - - else -> super.onOptionsItemSelected(item) - } - - private fun saveRouting() { - val content = binding.etRoutingContent.text.toString() - settingsStorage?.encode(requireArguments().getString(routing_arg), content) - activity?.toast(R.string.toast_success) - } - - fun scanQRcode(forReplace: Boolean): Boolean { -// try { -// startActivityForResult(Intent("com.google.zxing.client.android.SCAN") -// .addCategory(Intent.CATEGORY_DEFAULT) -// .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode) -// } catch (e: Exception) { - RxPermissions(requireActivity()) - .request(Manifest.permission.CAMERA) - .subscribe { - if (it) - if (forReplace) - scanQRCodeForReplace.launch(Intent(activity, ScannerActivity::class.java)) - else - scanQRCodeForAppend.launch(Intent(activity, ScannerActivity::class.java)) - else - activity?.toast(R.string.toast_permission_denied) - } -// } - return true - } - - private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == RESULT_OK) { - val content = it.data?.getStringExtra("SCAN_RESULT") - binding.etRoutingContent.text = Utils.getEditable(content) - } - } - - private val scanQRCodeForAppend = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == RESULT_OK) { - val content = it.data?.getStringExtra("SCAN_RESULT") - binding.etRoutingContent.text = Utils.getEditable("${binding.etRoutingContent.text},$content") - } - } - - fun setDefaultRules(): Boolean { - var url = AppConfig.v2rayCustomRoutingListUrl - var tag = "" - when (requireArguments().getString(routing_arg)) { - AppConfig.PREF_V2RAY_ROUTING_AGENT -> { - tag = AppConfig.TAG_PROXY - } - - AppConfig.PREF_V2RAY_ROUTING_DIRECT -> { - tag = AppConfig.TAG_DIRECT - } - - AppConfig.PREF_V2RAY_ROUTING_BLOCKED -> { - tag = AppConfig.TAG_BLOCKED - } - } - url += tag - - activity?.toast(R.string.msg_downloading_content) - lifecycleScope.launch(Dispatchers.IO) { - val content = Utils.getUrlContext(url, 5000) - launch(Dispatchers.Main) { - val routingList = if (TextUtils.isEmpty(content)) { - Utils.readTextFromAssets(activity?.v2RayApplication, "custom_routing_$tag") - } else { - content - } - binding.etRoutingContent.text = Utils.getEditable(routingList) - saveRouting() - //toast(R.string.toast_success) - } - } - return true - } -} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt index aa1a649e..818c211c 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/ui/SettingsActivity.kt @@ -43,8 +43,6 @@ class SettingsActivity : BaseActivity() { private val localDnsPort by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_PORT) } private val vpnDns by lazy { findPreference(AppConfig.PREF_VPN_DNS) } - private val routingCustom by lazy { findPreference(AppConfig.PREF_ROUTING_CUSTOM) } - private val mux by lazy { findPreference(AppConfig.PREF_MUX_ENABLED) } private val muxConcurrency by lazy { findPreference(AppConfig.PREF_MUX_CONCURRENCY) } private val muxXudpConcurrency by lazy { findPreference(AppConfig.PREF_MUX_XUDP_CONCURRENCY) } @@ -88,11 +86,6 @@ class SettingsActivity : BaseActivity() { true } - routingCustom?.setOnPreferenceClickListener { - startActivity(Intent(activity, RoutingSettingsActivity::class.java)) - false - } - mux?.setOnPreferenceChangeListener { _, newValue -> updateMux(newValue as Boolean) true @@ -251,7 +244,6 @@ class SettingsActivity : BaseActivity() { listOf( AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, - AppConfig.PREF_ROUTING_MODE, AppConfig.PREF_MUX_XUDP_QUIC, AppConfig.PREF_FRAGMENT_PACKETS, AppConfig.PREF_LANGUAGE, diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt index 87e3e74e..2286405d 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/AngConfigManager.kt @@ -65,7 +65,6 @@ object AngConfigManager { // AppConfig.PREF_HTTP_PORT, // AppConfig.PREF_LOGLEVEL, // AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, -// AppConfig.PREF_ROUTING_MODE, // AppConfig.PREF_V2RAY_ROUTING_AGENT, // AppConfig.PREF_V2RAY_ROUTING_BLOCKED, // AppConfig.PREF_V2RAY_ROUTING_DIRECT, diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MmkvManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MmkvManager.kt index 2215a2ba..5d7d5156 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MmkvManager.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MmkvManager.kt @@ -2,8 +2,10 @@ package com.v2ray.ang.util import com.google.gson.Gson import com.tencent.mmkv.MMKV +import com.v2ray.ang.AppConfig.PREF_ROUTING_RULESET import com.v2ray.ang.dto.AssetUrlItem import com.v2ray.ang.dto.ProfileItem +import com.v2ray.ang.dto.RulesetItem import com.v2ray.ang.dto.ServerAffiliationInfo import com.v2ray.ang.dto.ServerConfig import com.v2ray.ang.dto.SubscriptionItem @@ -225,4 +227,17 @@ object MmkvManager { } return null } + + fun decodeRoutingRulesets(): MutableList? { + val ruleset = settingsStorage.decodeString(PREF_ROUTING_RULESET) + if (ruleset.isNullOrEmpty()) return null + return Gson().fromJson(ruleset, Array::class.java).toMutableList() + } + + fun encodeRoutingRulesets(rulesetList: MutableList?) { + if (rulesetList.isNullOrEmpty()) + settingsStorage.encode(PREF_ROUTING_RULESET, "") + else + settingsStorage.encode(PREF_ROUTING_RULESET, Gson().toJson(rulesetList)) + } } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/SettingsManager.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/SettingsManager.kt new file mode 100644 index 00000000..c05422bb --- /dev/null +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/SettingsManager.kt @@ -0,0 +1,73 @@ +package com.v2ray.ang.util + +import android.content.Context +import android.text.TextUtils +import android.util.Log +import com.google.gson.Gson +import com.v2ray.ang.dto.RulesetItem + +object SettingsManager { + + fun initRoutingRulesets(context: Context) { + Log.d("=====", "initRoutingRuleset") + val exist = MmkvManager.decodeRoutingRulesets() + + if (exist.isNullOrEmpty()) { + Log.d("=====", "isNullOrEmpty") + val assets = Utils.readTextFromAssets(context, "custom_routing_white") + if (TextUtils.isEmpty(assets)) { + return + } + + val rulesetList = Gson().fromJson(assets, Array::class.java).toMutableList() + Log.d("=====", "rulesetList==" + rulesetList.count()) + MmkvManager.encodeRoutingRulesets(rulesetList) + } + } + + fun resetRoutingRulesets(context: Context) { + MmkvManager.encodeRoutingRulesets(null) + initRoutingRulesets(context) + } + + fun getRoutingRuleset(index: Int): RulesetItem? { + if (index < 0) return null + + val rulesetList = MmkvManager.decodeRoutingRulesets() + if (rulesetList.isNullOrEmpty()) return null + + return rulesetList[index] + } + + fun saveRoutingRuleset(index: Int, ruleset: RulesetItem?) { + if (ruleset == null) return + + val rulesetList = MmkvManager.decodeRoutingRulesets() + if (rulesetList.isNullOrEmpty()) return + + if (index < 0 || index >= rulesetList.count()) { + rulesetList.add(ruleset) + } else { + rulesetList[index] = ruleset + } + MmkvManager.encodeRoutingRulesets(rulesetList) + } + + fun removeRoutingRuleset(index: Int) { + if (index < 0) return + + val rulesetList = MmkvManager.decodeRoutingRulesets() + if (rulesetList.isNullOrEmpty()) return + + rulesetList.removeAt(index) + MmkvManager.encodeRoutingRulesets(rulesetList) + } + + fun routingRulesetsBypassLan(): Boolean { + val rulesetItems = MmkvManager.decodeRoutingRulesets() + val exist = rulesetItems?.any { it.enabled && it.domain?.contains(":private") == true } + return exist == true + } + + +} diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt index 9d1f6226..a3387874 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/V2rayConfigUtil.kt @@ -14,11 +14,12 @@ import com.v2ray.ang.AppConfig.TAG_PROXY import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4 import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V6 import com.v2ray.ang.dto.EConfigType -import com.v2ray.ang.dto.ERoutingMode +import com.v2ray.ang.dto.RulesetItem import com.v2ray.ang.dto.ServerConfig import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP +import com.v2ray.ang.dto.V2rayConfig.RoutingBean.RulesBean import com.v2ray.ang.util.MmkvManager.settingsStorage object V2rayConfigUtil { @@ -40,7 +41,7 @@ object V2rayConfigUtil { } val result = getV2rayNonCustomConfig(context, config) - //Log.d(ANG_PACKAGE, result.content) + Log.d(ANG_PACKAGE, result.content) return result } catch (e: Exception) { e.printStackTrace() @@ -167,84 +168,13 @@ object V2rayConfigUtil { private fun routing(v2rayConfig: V2rayConfig): Boolean { try { - val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) - ?: ERoutingMode.BYPASS_LAN_MAINLAND.value - routingUserRule( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED) - .orEmpty(), TAG_BLOCKED, v2rayConfig - ) - if (routingMode == ERoutingMode.GLOBAL_DIRECT.value) { - routingUserRule( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) - .orEmpty(), TAG_DIRECT, v2rayConfig - ) - routingUserRule( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) - .orEmpty(), TAG_PROXY, v2rayConfig - ) - } else { - routingUserRule( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) - .orEmpty(), TAG_PROXY, v2rayConfig - ) - routingUserRule( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) - .orEmpty(), TAG_DIRECT, v2rayConfig - ) + v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) ?: "IPIfNonMatch" + + val rulesetItems = MmkvManager.decodeRoutingRulesets() + rulesetItems?.forEach { key -> + routingUserRule(key, v2rayConfig) } - - v2rayConfig.routing.domainStrategy = - settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) - ?: "IPIfNonMatch" - - // Hardcode googleapis.cn gstatic.com - val googleapisRoute = V2rayConfig.RoutingBean.RulesBean( - outboundTag = TAG_PROXY, - domain = arrayListOf("domain:googleapis.cn", "domain:gstatic.com") - ) - - when (routingMode) { - ERoutingMode.BYPASS_LAN.value -> { - routingGeo("", "private", TAG_DIRECT, v2rayConfig) - } - - ERoutingMode.BYPASS_MAINLAND.value -> { - routingGeo("", "cn", TAG_DIRECT, v2rayConfig) - v2rayConfig.routing.rules.add(0, googleapisRoute) - } - - ERoutingMode.BYPASS_LAN_MAINLAND.value -> { - routingGeo("", "private", TAG_DIRECT, v2rayConfig) - routingGeo("", "cn", TAG_DIRECT, v2rayConfig) - v2rayConfig.routing.rules.add(0, googleapisRoute) - } - - ERoutingMode.GLOBAL_DIRECT.value -> { - val globalDirect = V2rayConfig.RoutingBean.RulesBean( - outboundTag = TAG_DIRECT, - ) - if (v2rayConfig.routing.domainStrategy != "IPIfNonMatch") { - globalDirect.port = "0-65535" - } else { - globalDirect.ip = arrayListOf("0.0.0.0/0", "::/0") - } - v2rayConfig.routing.rules.add(globalDirect) - } - } - - if (routingMode != ERoutingMode.GLOBAL_DIRECT.value) { - val globalProxy = V2rayConfig.RoutingBean.RulesBean( - outboundTag = TAG_PROXY, - ) - if (v2rayConfig.routing.domainStrategy != "IPIfNonMatch") { - globalProxy.port = "0-65535" - } else { - globalProxy.ip = arrayListOf("0.0.0.0/0", "::/0") - } - v2rayConfig.routing.rules.add(globalProxy) - } - } catch (e: Exception) { e.printStackTrace() return false @@ -252,73 +182,35 @@ object V2rayConfigUtil { return true } - private fun routingGeo(ipOrDomain: String, code: String, tag: String, v2rayConfig: V2rayConfig) { + private fun routingUserRule(item: RulesetItem?, v2rayConfig: V2rayConfig) { try { - if (!TextUtils.isEmpty(code)) { - //IP - if (ipOrDomain == "ip" || ipOrDomain == "") { - val rulesIP = V2rayConfig.RoutingBean.RulesBean() - rulesIP.outboundTag = tag - rulesIP.ip = ArrayList() - rulesIP.ip?.add("geoip:$code") - v2rayConfig.routing.rules.add(rulesIP) - } - - if (ipOrDomain == "domain" || ipOrDomain == "") { - //Domain - val rulesDomain = V2rayConfig.RoutingBean.RulesBean() - rulesDomain.outboundTag = tag - rulesDomain.domain = ArrayList() - rulesDomain.domain?.add("geosite:$code") - v2rayConfig.routing.rules.add(rulesDomain) - } + if (item == null || !item.enabled) { + return } + + val rule = Gson().fromJson(Gson().toJson(item), RulesBean::class.java) ?: return + + v2rayConfig.routing.rules.add(rule) + } catch (e: Exception) { e.printStackTrace() } } - private fun routingUserRule(userRule: String, tag: String, v2rayConfig: V2rayConfig) { - try { - if (!TextUtils.isEmpty(userRule)) { - //Domain - val rulesDomain = V2rayConfig.RoutingBean.RulesBean() - rulesDomain.outboundTag = tag - rulesDomain.domain = ArrayList() + private fun userRule2Domain(tag: String): ArrayList { + val domain = ArrayList() - //IP - val rulesIP = V2rayConfig.RoutingBean.RulesBean() - rulesIP.outboundTag = tag - rulesIP.ip = ArrayList() - - userRule.split(",").map { it.trim() }.forEach { - if (it.startsWith("ext:") && it.contains("geoip")) { - rulesIP.ip?.add(it) - } else if (Utils.isIpAddress(it) || it.startsWith("geoip:")) { - rulesIP.ip?.add(it) - } else if (it.isNotEmpty()) { - rulesDomain.domain?.add(it) + val rulesetItems = MmkvManager.decodeRoutingRulesets() + rulesetItems?.forEach { key -> + if (key != null && key.enabled && key.outboundTag == tag && !key.domain.isNullOrEmpty()) { + key.domain?.forEach { + if (it.startsWith("geosite:") || it.startsWith("domain:")) { + domain.add(it) } } - if ((rulesDomain.domain?.size ?: 0) > 0) { - v2rayConfig.routing.rules.add(rulesDomain) - } - if ((rulesIP.ip?.size ?: 0) > 0) { - v2rayConfig.routing.rules.add(rulesIP) - } } - } catch (e: Exception) { - e.printStackTrace() } - } - private fun userRule2Domain(userRule: String): ArrayList { - val domain = ArrayList() - userRule.split(",").map { it.trim() }.forEach { - if (it.startsWith("geosite:") || it.startsWith("domain:")) { - domain.add(it) - } - } return domain } @@ -326,14 +218,8 @@ object V2rayConfigUtil { try { if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) { val geositeCn = arrayListOf("geosite:cn") - val proxyDomain = userRule2Domain( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) - .orEmpty() - ) - val directDomain = userRule2Domain( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) - .orEmpty() - ) + val proxyDomain = userRule2Domain(TAG_PROXY) + val directDomain = userRule2Domain(TAG_DIRECT) // fakedns with all domains to make it always top priority v2rayConfig.dns.servers?.add( 0, @@ -404,10 +290,7 @@ object V2rayConfigUtil { //remote Dns val remoteDns = Utils.getRemoteDnsServers() - val proxyDomain = userRule2Domain( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) - .orEmpty() - ) + val proxyDomain = userRule2Domain(TAG_PROXY) remoteDns.forEach { servers.add(it) } @@ -424,16 +307,9 @@ object V2rayConfigUtil { // domestic DNS val domesticDns = Utils.getDomesticDnsServers() - val directDomain = userRule2Domain( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) - .orEmpty() - ) - val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) - ?: ERoutingMode.BYPASS_LAN_MAINLAND.value - val isCnRoutingMode = - (routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) + val directDomain = userRule2Domain(TAG_DIRECT) + val isCnRoutingMode = directDomain.contains("geosite:cn") val geoipCn = arrayListOf("geoip:cn") - if (directDomain.size > 0) { servers.add( V2rayConfig.DnsBean.ServersBean( @@ -444,17 +320,6 @@ object V2rayConfigUtil { ) ) } - if (isCnRoutingMode) { - val geositeCn = arrayListOf("geosite:cn") - servers.add( - V2rayConfig.DnsBean.ServersBean( - domesticDns.first(), - 53, - geositeCn, - geoipCn - ) - ) - } if (Utils.isPureIpAddress(domesticDns.first())) { v2rayConfig.routing.rules.add( @@ -468,10 +333,7 @@ object V2rayConfigUtil { } //block dns - val blkDomain = userRule2Domain( - settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED) - .orEmpty() - ) + val blkDomain = userRule2Domain(TAG_BLOCKED) if (blkDomain.size > 0) { hosts.putAll(blkDomain.map { it to "127.0.0.1" }) } diff --git a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/viewmodel/SettingsViewModel.kt b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/viewmodel/SettingsViewModel.kt index 13183ec9..75e6fbd1 100644 --- a/V2rayNG/app/src/main/kotlin/com/v2ray/ang/viewmodel/SettingsViewModel.kt +++ b/V2rayNG/app/src/main/kotlin/com/v2ray/ang/viewmodel/SettingsViewModel.kt @@ -39,10 +39,6 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application AppConfig.PREF_LANGUAGE, AppConfig.PREF_UI_MODE_NIGHT, AppConfig.PREF_ROUTING_DOMAIN_STRATEGY, - AppConfig.PREF_ROUTING_MODE, - AppConfig.PREF_V2RAY_ROUTING_AGENT, - AppConfig.PREF_V2RAY_ROUTING_BLOCKED, - AppConfig.PREF_V2RAY_ROUTING_DIRECT, AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL, AppConfig.PREF_FRAGMENT_PACKETS, AppConfig.PREF_FRAGMENT_LENGTH, diff --git a/V2rayNG/app/src/main/res/drawable-night/ic_routing_24dp.xml b/V2rayNG/app/src/main/res/drawable-night/ic_routing_24dp.xml new file mode 100644 index 00000000..7002c085 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable-night/ic_routing_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/drawable/ic_routing_24dp.xml b/V2rayNG/app/src/main/res/drawable/ic_routing_24dp.xml new file mode 100644 index 00000000..9032a2e6 --- /dev/null +++ b/V2rayNG/app/src/main/res/drawable/ic_routing_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/V2rayNG/app/src/main/res/layout/activity_routing_edit.xml b/V2rayNG/app/src/main/res/layout/activity_routing_edit.xml new file mode 100644 index 00000000..68f8d53c --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_routing_edit.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/layout/activity_routing_setting.xml b/V2rayNG/app/src/main/res/layout/activity_routing_setting.xml new file mode 100644 index 00000000..825c1c50 --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/activity_routing_setting.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/layout/activity_routing_settings.xml b/V2rayNG/app/src/main/res/layout/activity_routing_settings.xml deleted file mode 100644 index a77a8062..00000000 --- a/V2rayNG/app/src/main/res/layout/activity_routing_settings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - 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 46c97385..19732a8e 100644 --- a/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml +++ b/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml @@ -80,12 +80,11 @@ android:orientation="horizontal"> - - - - - - - - - - - - - - - diff --git a/V2rayNG/app/src/main/res/layout/item_recycler_routing_setting.xml b/V2rayNG/app/src/main/res/layout/item_recycler_routing_setting.xml new file mode 100644 index 00000000..a42362db --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/item_recycler_routing_setting.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/V2rayNG/app/src/main/res/layout/item_recycler_sub_setting.xml b/V2rayNG/app/src/main/res/layout/item_recycler_sub_setting.xml index 738a7f5c..e5d8e4c0 100644 --- a/V2rayNG/app/src/main/res/layout/item_recycler_sub_setting.xml +++ b/V2rayNG/app/src/main/res/layout/item_recycler_sub_setting.xml @@ -51,7 +51,7 @@ + android:id="@+id/routing_setting" + android:icon="@drawable/ic_routing_24dp" + android:title="@string/routing_settings_title" /> diff --git a/V2rayNG/app/src/main/res/menu/menu_routing.xml b/V2rayNG/app/src/main/res/menu/menu_routing.xml deleted file mode 100644 index 7f40c92a..00000000 --- a/V2rayNG/app/src/main/res/menu/menu_routing.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/menu/menu_routing_setting.xml b/V2rayNG/app/src/main/res/menu/menu_routing_setting.xml new file mode 100644 index 00000000..afa71bc0 --- /dev/null +++ b/V2rayNG/app/src/main/res/menu/menu_routing_setting.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values-ar/strings.xml b/V2rayNG/app/src/main/res/values-ar/strings.xml index 1ede85b5..22bff425 100644 --- a/V2rayNG/app/src/main/res/values-ar/strings.xml +++ b/V2rayNG/app/src/main/res/values-ar/strings.xml @@ -158,11 +158,6 @@ تفضيل IPv6 تفضيل عنوان IPv6 وطرق التوجيه - التوجيه - استراتيجية النطاق - قواعد محددة مسبقًا - قواعد مخصصة - DNS البعيد (udp/tcp/https/quic) (اختياري) DNS @@ -251,13 +246,15 @@ بدء الخدمة تأكيد + استراتيجية النطاق إعدادات التوجيه مفصولة بفواصل (،)، تذكر الحفظ حفظ مسح - فحص واستبدال - فحص وإضافة - تعيين قواعد توجيه افتراضية + Routing Rule Settings + Add rule + Import ruleset + Existing rulesets will be deleted, are you sure to continue? التحقق من الاتصال يجري الاختبار… @@ -287,20 +284,6 @@ تصدير إلى الحافظة - - عنوان URL أو IP الوكيل - عنوان URL أو IP المباشر - عنوان URL أو IP المحظور - - - - وكيل عام - تجاوز عنوان الشبكة المحلية ثم الوكيل - تجاوز عنوان البر الرئيسي ثم الوكيل - تجاوز عنوان الشبكة المحلية والبر الرئيسي ثم الوكيل - مباشر عام - - VPN بروكسي فقط diff --git a/V2rayNG/app/src/main/res/values-bn/strings.xml b/V2rayNG/app/src/main/res/values-bn/strings.xml index db79075f..86967872 100644 --- a/V2rayNG/app/src/main/res/values-bn/strings.xml +++ b/V2rayNG/app/src/main/res/values-bn/strings.xml @@ -157,11 +157,6 @@ IPv6 অগ্রাধিকার দিন IPv6 ঠিকানা এবং রুটকে অগ্রাধিকার দিন - রাউটিং - ডোমেইন কৌশল - পূর্বনির্ধারিত নিয়ম - কাস্টম নিয়ম - রিমোট DNS (udp/tcp/https/quic)(ঐচ্ছিক) DNS @@ -248,13 +243,15 @@ সার্ভিস শুরু করুন নিশ্চিত করুন + ডোমেইন কৌশল রাউটিং সেটিংস কমা (,) দ্বারা আলাদা করুন, মনে রাখবেন সেভ করতে সেভ করুন মুছে ফেলুন - স্ক্যান করুন এবং প্রতিস্থাপন করুন - স্ক্যান করুন এবং যোগ করুন - ডিফল্ট রাউটিং নিয়ম সেট করুন + Routing Rule Settings + Add rule + Import ruleset + Existing rulesets will be deleted, are you sure to continue? সংযোগ পরীক্ষা করুন পরীক্ষা চলছে… @@ -283,20 +280,6 @@ ক্লিপবোর্ডে রপ্তানি করুন - - প্রক্সি URL বা IP - ডাইরেক্ট URL বা IP - ব্লকড URL বা IP - - - - গ্লোবাল প্রোক্সি - LAN ঠিকানা বাইপাস করে তারপর প্রোক্সি - মেইনল্যান্ড ঠিকানা বাইপাস করে তারপর প্রোক্সি - LAN এবং মেইনল্যান্ড ঠিকানা বাইপাস করে তারপর প্রোক্সি - গ্লোবাল ডাইরেক্ট - - VPN শুধুমাত্র প্রোক্সি diff --git a/V2rayNG/app/src/main/res/values-fa/strings.xml b/V2rayNG/app/src/main/res/values-fa/strings.xml index da8d52ff..08b5683d 100644 --- a/V2rayNG/app/src/main/res/values-fa/strings.xml +++ b/V2rayNG/app/src/main/res/values-fa/strings.xml @@ -155,11 +155,6 @@ ترجیح دادن IPv6 ترجیح دادن نشانی و مسیر های IPv6 - مسیریابی - استراتژی دامنه - قوانین از پیش تعریف شده - قوانین سفارشی - DNS از راه دور (اختیاری) DNS @@ -247,13 +242,15 @@ شروع خدمات تایید + استراتژی دامنه تنظیمات مسیریابی با کاما (,) از هم جدا شوند، ذخیره کردن فراموش نشود ذخیره پاک کردن - اسکن و جایگزین کنید - اسکن و اضافه کنید - قوانین مسیریابی پیش‌فرض را تنظیم کنید + Routing Rule Settings + Add rule + Import ruleset + Existing rulesets will be deleted, are you sure to continue? اتصال را بررسی کنید در حال آزمایش... @@ -282,20 +279,6 @@ خروجی گرفتن در کلیپ‌بورد - - نشانی اینترنتی یا آی‌پی پروکسی - نشانی اینترنتی یا آی‌پی مستقیم - نشانی اینترنتی یا آی‌پی مسدود شده - - - - پروکسی سراسری - دور زدن آدرس LAN و سپس پروکسی - دور زدن آدرس mainland و سپس پروکسی - دور زدن LAN و آدرس mainland و سپس پروکسی - مستقیم سراسری - - VPN فقط پروکسی diff --git a/V2rayNG/app/src/main/res/values-ru/strings.xml b/V2rayNG/app/src/main/res/values-ru/strings.xml index 1798634c..e5d5d42f 100644 --- a/V2rayNG/app/src/main/res/values-ru/strings.xml +++ b/V2rayNG/app/src/main/res/values-ru/strings.xml @@ -158,11 +158,6 @@ Предпочитать IPv6 Предпочитать IPv6-адреса и маршрутизацию - Маршрутизация - Доменная стратегия - Режим маршрутизации - Пользовательские правила - Удалённая DNS (UDP/TCP/HTTPS/QUIC) (необязательно) DNS @@ -251,13 +246,15 @@ Запуск службы Подтвердить + Доменная стратегия Настройки маршрутизации Введите требуемые IP/URL через запятую. Не забудьте сохранить изменения. Сохранить Очистить - Сканировать и заменить - Сканировать и добавить - Правила по умолчанию + Routing Rule Settings + Add rule + Import ruleset + Existing rulesets will be deleted, are you sure to continue? Проверить подключение Проверка… @@ -287,20 +284,6 @@ Экспорт в буфер обмена - - Проксируемые - Прямые - Блокируемые - - - - Все через прокси - Все, кроме LAN через прокси - Все, кроме Китая через прокси - Все, кроме LAN и Китая через прокси - Все напрямую - - VPN Только прокси diff --git a/V2rayNG/app/src/main/res/values-vi/strings.xml b/V2rayNG/app/src/main/res/values-vi/strings.xml index 4df0766f..38d244fd 100644 --- a/V2rayNG/app/src/main/res/values-vi/strings.xml +++ b/V2rayNG/app/src/main/res/values-vi/strings.xml @@ -157,11 +157,6 @@ Ưu tiên IPv6 Ưu tiên sử dụng địa chỉ IPv6 cho kết nối và định tuyến. - Định tuyến - Chiến lược tên miền (DomainStrategy) - Quy tắc được định nghĩa trước - Quy tắc tùy chỉnh - DNS ngoại quốc (UDP / TCP / HTTPS / QUIC) (Không bắt buộc) DNS @@ -250,13 +245,15 @@ Khởi động v2rayNG Xác nhận + Chiến lược tên miền (DomainStrategy) Cài đặt định tuyến Phân cách bằng dấu phẩy (,). Có thể tải xuống Rules mặc định để tham khảo ở menu ba chấm. Lưu lại Xoá - Quét QR và Thay thế - Quét QR và Nối thêm - Tải xuống Rules mặc định cho Trung Quốc + Routing Rule Settings + Add rule + Import ruleset + Existing rulesets will be deleted, are you sure to continue? Kiểm tra kết nối Đang kiểm tra kết nối mạng... @@ -281,20 +278,6 @@ Xuất gói vào Clipboard - - Proxy - Direct - Blocked - - - - Proxy toàn cầu - Bỏ qua địa chỉ LAN rồi Proxy - Bỏ qua địa chỉ nội địa rồi Proxy - Bỏ qua LAN và địa chỉ nội địa rồi Proxy - Kết nối trực tiếp toàn cầu - - Chế độ VPN Chế độ Proxy 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 4eaa1312..f3d12d6c 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -155,11 +155,6 @@ IPv6优先 App优先使用IPv6地址连接服务器,同时开启VPN的IPv6路由 - 路由设置 - 域名策略 - 预定义规则 - 自定义规则 - 远程DNS (udp/tcp/https/quic)(可选) DNS @@ -248,13 +243,15 @@ 启动服务 确定 + 域名策略 路由设置 - 用逗号(,)隔开,可以一行多个,记得保存 + 用逗号(,)隔开,可以一行多个 保存 清空 - 扫描并替换 - 扫描并追加 - 设置默认路由规则 + 路由规则设置 + 添加规则 + 导入预设规则集 + 将删除现有的规则集,是否确定继续? "检查网络连接" "测试中…" @@ -286,21 +283,6 @@ 导出至剪贴板 - share_method - - 代理的网址或IP - 直连的网址或IP - 阻止的网址或IP - - - - 全局代理 - 绕过局域网地址而后代理 - 绕过大陆地址而后代理 - 绕过局域网及大陆地址而后代理 - 全局直连 - - VPN 仅代理 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 fa53135f..c80ed2c1 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -156,11 +156,6 @@ IPv6 偏好 App 優先使用 IPv6 位址連線伺服器,同时開啟 VPN 的 IPv6 路由 - 轉送設定 - 網域策略 - 轉送模式 - 自訂轉送 - 遠端DNS (udp/tcp/https/quic)(可選) DNS @@ -249,13 +244,15 @@ 啟動服務 確定 + 網域策略 轉送設定 - 以半形逗號「,」分隔,並手動儲存 + 以半形逗號「,」分隔 儲存 清除 - 掃描並取代 - 掃描並附加 - 設定預設轉送規則 + 路由規則設定 + 新增規則 + 匯入預設規則集 + 將刪除現有的規則集,是否確定繼續? "測試連線能力" "測試中……" @@ -287,20 +284,6 @@ 匯出至剪貼簿 - - Proxy URL 或 IP - 直接連線 URL 或 IP - 已封鎖的 URL 或 IP - - - - 全域 Proxy - 略過區域網路的 Proxy - 略過中國大陸的 Proxy - 略過區域網路及中國大陸的 Proxy - 直接連線 - - VPN 僅 Proxy diff --git a/V2rayNG/app/src/main/res/values/arrays.xml b/V2rayNG/app/src/main/res/values/arrays.xml index 721aae04..535e6bd7 100644 --- a/V2rayNG/app/src/main/res/values/arrays.xml +++ b/V2rayNG/app/src/main/res/values/arrays.xml @@ -99,14 +99,6 @@ false - - 0 - 1 - 2 - 3 - 4 - - AsIs IPIfNonMatch @@ -205,4 +197,10 @@ 1 2 + + + proxy + direct + block + \ No newline at end of file diff --git a/V2rayNG/app/src/main/res/values/dimens.xml b/V2rayNG/app/src/main/res/values/dimens.xml index ab32f209..58510db5 100644 --- a/V2rayNG/app/src/main/res/values/dimens.xml +++ b/V2rayNG/app/src/main/res/values/dimens.xml @@ -7,7 +7,7 @@ 50dp 24dp 72dp - 108dp + 90dp 60dp 16dp 16dp diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index c9a090a3..fae291f1 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -161,11 +161,6 @@ Prefer IPv6 Prefer IPv6 address and routes - Routing - Domain strategy - Predefined rules - Custom rules - Remote DNS (udp/tcp/https/quic)(Optional) DNS @@ -254,13 +249,23 @@ Start Service Confirm + Domain strategy Routing Settings - Separated by commas(,),remember to save + Separated by commas(,) Save Clear - Scan and replace - Scan and append - set default routing rules + Routing Rule Settings + Add rule + Import ruleset + Existing rulesets will be deleted, are you sure to continue? + domain + ip + port + protocol + [http,tls,bittorrent] + network"[udp|tcp]" + [udp|tcp] + outboundTag Check Connectivity Testing… @@ -290,20 +295,6 @@ Export to clipboard - - proxy URL or IP - direct URL or IP - blocked URL or IP - - - - Global proxy - Bypassing the LAN address then proxy - Bypass mainland address then proxy - Bypassing LAN and mainland address then proxy - Global direct - - VPN Proxy only diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml index 9f5728de..ca13d534 100644 --- a/V2rayNG/app/src/main/res/xml/pref_settings.xml +++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml @@ -42,30 +42,6 @@ android:title="@string/title_pref_vpn_dns" /> - - - - - - - - -